Selaa lähdekoodia

Merge branch 'master' into zzf-account

zhangzf 2 viikkoa sitten
vanhempi
commit
9e7021132f
100 muutettua tiedostoa jossa 4259 lisäystä ja 2624 poistoa
  1. 14 1
      .env.example
  2. 5 0
      .gitignore
  3. 106 11
      README.md
  4. 8 0
      next.config.ts
  5. 2 1
      package.json
  6. 32 10
      pnpm-lock.yaml
  7. BIN
      public/image/payment/afterpay.png
  8. BIN
      public/image/payment/credit.png
  9. BIN
      public/image/payment/klarna.png
  10. BIN
      public/image/payment/paypal.png
  11. 19 19
      src/app/(checkout)/checkout/_components/AddressResultDisplay.tsx
  12. 11 15
      src/app/(checkout)/checkout/_components/BillingAddressCheckout.tsx
  13. 0 70
      src/app/(checkout)/checkout/_components/CheckoutAddressModal.tsx
  14. 187 0
      src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx
  15. 15 0
      src/app/(checkout)/checkout/_components/CheckoutShippingMethodLoading.tsx
  16. 462 85
      src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx
  17. 195 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  18. 409 0
      src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx
  19. 120 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx
  20. 12 0
      src/app/(checkout)/checkout/_components/LoadingCheckoutPlaceOrder.tsx
  21. 13 0
      src/app/(checkout)/checkout/_components/LoadingPaymentMethod.tsx
  22. 301 0
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalApplepayButton.tsx
  23. 250 0
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalButton.tsx
  24. 41 0
      src/app/(checkout)/checkout/_components/PaymentMethodAdditional/AirwallexCardInput.tsx
  25. 195 0
      src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx
  26. 15 0
      src/app/(checkout)/checkout/_components/PromotionsDetails.tsx
  27. 9 12
      src/app/(checkout)/checkout/_components/ShippingAddressCheckout.tsx
  28. 136 0
      src/app/(checkout)/checkout/_components/ShippingMethodCheckout.tsx
  29. 87 0
      src/app/(checkout)/checkout/continuetopay/page.tsx
  30. 18 30
      src/app/(checkout)/checkout/page.tsx
  31. 6 6
      src/app/(checkout)/layout.tsx
  32. 101 0
      src/app/(public)/paymentresult/_components/CancelOrFailure.tsx
  33. 20 0
      src/app/(public)/paymentresult/_components/Faqs.tsx
  34. 200 0
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  35. 34 0
      src/app/(public)/paymentresult/_components/SkeletonCancelOrFailure.tsx
  36. 124 0
      src/app/(public)/paymentresult/_components/SuccessPageSkeleton.tsx
  37. 64 0
      src/app/(public)/paymentresult/result/page.tsx
  38. 2 2
      src/app/(public)/product/[...urlProduct]/page.tsx
  39. 1 1
      src/app/(public)/product/_components/ProductAddToCart.tsx
  40. 2 2
      src/app/(public)/product/_components/ProductInformation.tsx
  41. 0 17
      src/app/(public)/success/page.tsx
  42. 20 0
      src/app/api/clearCartCookie/route.ts
  43. 15 8
      src/app/api/graphql/route.ts
  44. 56 1
      src/app/globals.css
  45. 93 7
      src/app/layout.tsx
  46. 18 59
      src/components/cart/CartModal.tsx
  47. 0 30
      src/components/cart/OrderDetail.tsx
  48. 2 12
      src/components/catalog/product/ProductCard.tsx
  49. 1 1
      src/components/catalog/product/RelatedProductsSection.tsx
  50. 0 161
      src/components/checkout/checkout-cart/CartItemAccordian.tsx
  51. 0 128
      src/components/checkout/checkout-cart/CheckoutCart.tsx
  52. 0 68
      src/components/checkout/index.tsx
  53. 0 140
      src/components/checkout/stepper/Email.tsx
  54. 0 540
      src/components/checkout/stepper/GuestAddAdressForm.tsx
  55. 0 73
      src/components/checkout/stepper/ProceedToCheckout.tsx
  56. 0 169
      src/components/checkout/stepper/index.tsx
  57. 0 222
      src/components/checkout/stepper/payment/PaymentMethod.tsx
  58. 0 37
      src/components/checkout/stepper/payment/index.tsx
  59. 0 101
      src/components/checkout/stepper/review/OrderReview.tsx
  60. 0 29
      src/components/checkout/stepper/review/index.tsx
  61. 0 219
      src/components/checkout/stepper/shipping/ShippingMethod.tsx
  62. 0 37
      src/components/checkout/stepper/shipping/index.tsx
  63. 0 73
      src/components/checkout/success/EmptyCart.tsx
  64. 0 24
      src/components/checkout/type.ts
  65. 1 1
      src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx
  66. 67 0
      src/components/common/ConfirmModal.tsx
  67. 74 0
      src/components/common/CountDown.tsx
  68. 10 0
      src/components/common/portal/Portal.tsx
  69. 7 3
      src/components/Portal.tsx
  70. 5 4
      src/components/customer/credentials/CredentialModal.tsx
  71. 1 1
      src/components/layout/navbar/MobileMenuTrigger.tsx
  72. 16 0
      src/components/layout/navbar/TitleHeader.tsx
  73. 16 29
      src/components/layout/navbar/index.tsx
  74. 0 76
      src/components/theme/ui/AddToCartButton.tsx
  75. 138 0
      src/components/theme/ui/CommonModal.tsx
  76. 0 3
      src/components/theme/ui/InputText.tsx
  77. 1 2
      src/components/theme/ui/PhoneNumberInput/PhoneNumberInput.tsx
  78. 0 3
      src/components/theme/ui/Select.tsx
  79. 15 0
      src/components/theme/ui/kernel/KernelProvider.tsx
  80. 31 0
      src/components/theme/ui/kernel/confirm/api.ts
  81. 46 0
      src/components/theme/ui/kernel/confirm/host.tsx
  82. 17 0
      src/components/theme/ui/kernel/confirm/store.ts
  83. 10 0
      src/components/theme/ui/kernel/confirm/useConfirm.ts
  84. 22 0
      src/components/theme/ui/kernel/core/store.ts
  85. 19 0
      src/components/theme/ui/kernel/loading/api.ts
  86. 20 0
      src/components/theme/ui/kernel/loading/host.tsx
  87. 5 0
      src/components/theme/ui/kernel/loading/store.ts
  88. 10 0
      src/components/theme/ui/kernel/loading/useLoading.ts
  89. 34 17
      src/graphql/cart/mutations/AddProductToCart.ts
  90. 32 12
      src/graphql/cart/mutations/CreateMergeCart.ts
  91. 27 9
      src/graphql/cart/mutations/GetCartItem.ts
  92. 41 25
      src/graphql/cart/mutations/RemoveCartItem.ts
  93. 30 11
      src/graphql/cart/mutations/UpdateCartItems.ts
  94. 8 0
      src/graphql/cart/mutations/readme.md
  95. 22 0
      src/graphql/checkout/mutations/CreateCancelOrder.ts
  96. 37 3
      src/graphql/checkout/mutations/CreateCheckoutOrder.ts
  97. 3 2
      src/graphql/checkout/mutations/CreateCheckoutPaymentMethod.ts
  98. 3 2
      src/graphql/checkout/mutations/CreateCheckoutShippingMethod.ts
  99. 70 0
      src/graphql/checkout/mutations/CreatePaymentCallback.ts
  100. 0 0
      src/graphql/checkout/mutations/CreatePaymentReplay.ts

+ 14 - 1
.env.example

@@ -7,5 +7,18 @@ NEXTAUTH_URL=http://localhost:3001
 # NextAuth 使用 secret(下面的值)对 session中的token 对象进行 签名 + 加密,生成一个 JWT 字符串,解密这个JWT字符串就可以还原token对象。
 # NextAuth 使用 secret(下面的值)对 session中的token 对象进行 签名 + 加密,生成一个 JWT 字符串,解密这个JWT字符串就可以还原token对象。
 NEXTAUTH_SECRET=9jstKgQ1Q22PlDDhPtXTZ6G4MEzHGDJIkebcd/5UyZ4=
 NEXTAUTH_SECRET=9jstKgQ1Q22PlDDhPtXTZ6G4MEzHGDJIkebcd/5UyZ4=
 
 
+# paypal client id
+PAYPAL_SANDBOX_CLIENT_ID=
+PAYPAL_PRODUCTION_CLIENT_ID=
+
 # Application Settings
 # Application Settings
-COMPANY_NAME=Your Company Name
+COMPANY_NAME=Your Company Name
+
+# store name
+STORE_NAME=Asteria
+# 默认货币
+DEFAULT_CURRENCY=USD
+# 默认语言
+DEFAULT_LOCAL=en
+# 默认渠道
+DEFAULT_CHANNEL=default

+ 5 - 0
.gitignore

@@ -23,6 +23,7 @@
 # misc
 # misc
 .DS_Store
 .DS_Store
 *.pem
 *.pem
+.vscode
 
 
 # debug
 # debug
 npm-debug.log*
 npm-debug.log*
@@ -34,6 +35,8 @@ yarn-error.log*
 .env
 .env
 .env.production
 .env.production
 .env.local
 .env.local
+.env.development.local
+.env.test
 
 
 # vercel
 # vercel
 .vercel
 .vercel
@@ -42,5 +45,7 @@ yarn-error.log*
 *.tsbuildinfo
 *.tsbuildinfo
 next-env.d.ts
 next-env.d.ts
 
 
+# .well-known
+/public/.well-known
 
 
 ~
 ~

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 106 - 11
README.md


+ 8 - 0
next.config.ts

@@ -13,6 +13,14 @@ const nextConfig: NextConfig = {
   async headers() {
   async headers() {
     return configHeader;
     return configHeader;
   },
   },
+  async rewrites() {
+    return [
+      {
+        source: '/:slug.html',
+        destination: '/product/:slug',
+      },
+    ];
+  },
   compress: true, 
   compress: true, 
   experimental: {
   experimental: {
     optimizePackageImports: ["lodash", "date-fns"],
     optimizePackageImports: ["lodash", "date-fns"],

+ 2 - 1
package.json

@@ -11,6 +11,7 @@
     "package-version": "npx npm-check-updates"
     "package-version": "npx npm-check-updates"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@airwallex/components-sdk": "^1.32.0",
     "@apollo/client": "^4.1.9",
     "@apollo/client": "^4.1.9",
     "@apollo/client-integration-nextjs": "^0.14.5",
     "@apollo/client-integration-nextjs": "^0.14.5",
     "@heroicons/react": "^2.2.0",
     "@heroicons/react": "^2.2.0",
@@ -31,7 +32,7 @@
     "@heroui/tooltip": "^2.2.29",
     "@heroui/tooltip": "^2.2.29",
     "@heroui/use-disclosure": "^2.2.19",
     "@heroui/use-disclosure": "^2.2.19",
     "@internationalized/date": "3.12.0",
     "@internationalized/date": "3.12.0",
-    "@paypal/react-paypal-js": "^9.2.0",
+    "@paypal/react-paypal-js": "^10.1.2",
     "@react-aria/visually-hidden": "^3.8.31",
     "@react-aria/visually-hidden": "^3.8.31",
     "@react-types/shared": "^3.33.1",
     "@react-types/shared": "^3.33.1",
     "@reduxjs/toolkit": "^2.10.1",
     "@reduxjs/toolkit": "^2.10.1",

+ 32 - 10
pnpm-lock.yaml

@@ -12,6 +12,9 @@ importers:
 
 
   .:
   .:
     dependencies:
     dependencies:
+      '@airwallex/components-sdk':
+        specifier: ^1.32.0
+        version: 1.32.0
       '@apollo/client':
       '@apollo/client':
         specifier: ^4.1.9
         specifier: ^4.1.9
         version: 4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
         version: 4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
@@ -73,8 +76,8 @@ importers:
         specifier: 3.12.0
         specifier: 3.12.0
         version: 3.12.0
         version: 3.12.0
       '@paypal/react-paypal-js':
       '@paypal/react-paypal-js':
-        specifier: ^9.2.0
-        version: 9.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+        specifier: ^10.1.2
+        version: 10.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       '@react-aria/visually-hidden':
       '@react-aria/visually-hidden':
         specifier: ^3.8.31
         specifier: ^3.8.31
         version: 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
         version: 3.9.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -175,6 +178,12 @@ packages:
       react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
       react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
       react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
       react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
 
 
+  '@airwallex/airtracker@3.2.0':
+    resolution: {integrity: sha512-PKE5N38ajTVg6ph9JzLpWsICNjqLtf/wWudNVU3UPX9SVy2I5s5ITc281sMSD8+LIE6RJoGjGTO+VYP/io5kig==}
+
+  '@airwallex/components-sdk@1.32.0':
+    resolution: {integrity: sha512-IRrYOyCIWw/cQcndm/n11zx8b7hj2wYF7TD2YLmds9lXIQoir214JF99i1QJAyMsRR//nIRkNFTB2Ta2/etYGw==}
+
   '@alloc/quick-lru@5.2.0':
   '@alloc/quick-lru@5.2.0':
     resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
     resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
@@ -962,11 +971,11 @@ packages:
   '@panva/hkdf@1.2.1':
   '@panva/hkdf@1.2.1':
     resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
     resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
 
 
-  '@paypal/paypal-js@9.7.0':
-    resolution: {integrity: sha512-eUQVZWTEhXhaPsYDmfUaOeV5zfeLUn7kCE7/BXm6uAtMhUoK0S7uPtUzosqfH1vZgjfUSyTzghDJtYxXyRRZig==}
+  '@paypal/paypal-js@10.0.3':
+    resolution: {integrity: sha512-GtY3PID9Ey76fIJBAV8PobIwdH6Nsbm7MZlqFwB+MAPQEtVEwGe1EHrsZpb4AK0iuJNs9E3gaB75kgpFSpAEJQ==}
 
 
-  '@paypal/react-paypal-js@9.2.0':
-    resolution: {integrity: sha512-wckuxilT+fYYwg0TYbEbxkclYeJnhIlThpNtV9RLXQA6uBbt5xPIzGv9wt2ykQTLiNIqhXhw1Hlz5yxUeCteKQ==}
+  '@paypal/react-paypal-js@10.1.2':
+    resolution: {integrity: sha512-nPzTw58U+6Emv5qQDxVP5AdUL1gvdU5b1rHGOOTvB/WHmC2Ebn0soCOMNgP8xsvdz+Kshy2TJru0ERqC2zFkTA==}
     peerDependencies:
     peerDependencies:
       react: ^16.8.0 || ^17 || ^18 || ^19
       react: ^16.8.0 || ^17 || ^18 || ^19
       react-dom: ^16.8.0 || ^17 || ^18 || ^19
       react-dom: ^16.8.0 || ^17 || ^18 || ^19
@@ -1552,6 +1561,9 @@ packages:
   '@swc/helpers@0.5.21':
   '@swc/helpers@0.5.21':
     resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
     resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
 
 
+  '@swc/helpers@0.5.23':
+    resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
   '@tailwindcss/node@4.3.0':
   '@tailwindcss/node@4.3.0':
     resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
     resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
 
 
@@ -3463,6 +3475,12 @@ snapshots:
       react-transition-group: 4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       react-transition-group: 4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       use-sync-external-store: 1.6.0(react@19.2.5)
       use-sync-external-store: 1.6.0(react@19.2.5)
 
 
+  '@airwallex/airtracker@3.2.0': {}
+
+  '@airwallex/components-sdk@1.32.0':
+    dependencies:
+      '@airwallex/airtracker': 3.2.0
+
   '@alloc/quick-lru@5.2.0': {}
   '@alloc/quick-lru@5.2.0': {}
 
 
   '@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
   '@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
@@ -4495,13 +4513,13 @@ snapshots:
 
 
   '@panva/hkdf@1.2.1': {}
   '@panva/hkdf@1.2.1': {}
 
 
-  '@paypal/paypal-js@9.7.0':
+  '@paypal/paypal-js@10.0.3':
     dependencies:
     dependencies:
       promise-polyfill: 8.3.0
       promise-polyfill: 8.3.0
 
 
-  '@paypal/react-paypal-js@9.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
+  '@paypal/react-paypal-js@10.1.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
     dependencies:
     dependencies:
-      '@paypal/paypal-js': 9.7.0
+      '@paypal/paypal-js': 10.0.3
       '@paypal/sdk-constants': 1.0.158
       '@paypal/sdk-constants': 1.0.158
       react: 19.2.5
       react: 19.2.5
       react-dom: 19.2.5(react@19.2.5)
       react-dom: 19.2.5(react@19.2.5)
@@ -4984,7 +5002,7 @@ snapshots:
   '@react-spectrum/provider@3.11.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
   '@react-spectrum/provider@3.11.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
     dependencies:
     dependencies:
       '@adobe/react-spectrum': 3.47.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       '@adobe/react-spectrum': 3.47.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
-      '@swc/helpers': 0.5.21
+      '@swc/helpers': 0.5.23
       react: 19.2.5
       react: 19.2.5
       react-dom: 19.2.5(react@19.2.5)
       react-dom: 19.2.5(react@19.2.5)
 
 
@@ -5363,6 +5381,10 @@ snapshots:
     dependencies:
     dependencies:
       tslib: 2.8.1
       tslib: 2.8.1
 
 
+  '@swc/helpers@0.5.23':
+    dependencies:
+      tslib: 2.8.1
+
   '@tailwindcss/node@4.3.0':
   '@tailwindcss/node@4.3.0':
     dependencies:
     dependencies:
       '@jridgewell/remapping': 2.3.5
       '@jridgewell/remapping': 2.3.5

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";
 "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 && 
     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;
         return true;
     }
     }
@@ -23,7 +23,7 @@ export default function AddressResultDisplay({
     shippingAddress,
     shippingAddress,
     onAddressModalOpenClick,
     onAddressModalOpenClick,
 } : {
 } : {
-    shippingAddress: ShipAddressFormData | null;
+    shippingAddress: CartAddress | null;
     onAddressModalOpenClick: () => void;
     onAddressModalOpenClick: () => void;
 }) {
 }) {
     const addressIsAvailable: boolean = isAddressAvailable(shippingAddress);
     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">
                 <div className="border-b-1 flex h-11.25 items-center justify-between">
                     <p className="text-ly-13 font-medium">
                     <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> 
                         <span className="border-r border-ly-gray h-3.5 mx-2"></span> 
-                        {shippingAddress.shippingPhoneNumber}
+                        {shippingAddress.phone}
                     </p>
                     </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>
                     <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>
                 </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>
                         <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>
                     </svg>
                     <span className="text-ly-12 ml-2.5">
                     <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>
                     </span>
                 </div>
                 </div>
             </div>       
             </div>       

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

@@ -19,17 +19,13 @@ import {
     IS_VALID_PHONECODE,
     IS_VALID_PHONECODE,
     IS_VALID_FULL_PHONE 
     IS_VALID_FULL_PHONE 
 } from "@utils/constants";
 } from "@utils/constants";
-import { Country } from "@/types/checkout/type";
 import InputText from "@/components/theme/ui/InputText";
 import InputText from "@/components/theme/ui/InputText";
 import Select from "@/components/theme/ui/Select";
 import Select from "@/components/theme/ui/Select";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
-
-export default function BillingAddressCheckout ({
-    countries,
-}:{
-    countries: Country[];
-}) {
+import { useConfig } from "@utils/hooks/useConfig";
+export default function BillingAddressCheckout () {
+    const {countries} = useConfig();
     const { 
     const { 
         getValues,
         getValues,
         setValue,
         setValue,
@@ -151,8 +147,8 @@ export default function BillingAddressCheckout ({
         <section className="w-full mt-4"> 
         <section className="w-full mt-4"> 
             <div className="box-border w-full p-3 bg-ly-lightgray">
             <div className="box-border w-full p-3 bg-ly-lightgray">
                 <div className="flex justify-between items-center">
                 <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"
                         <input className="absolute w-full h-full top-0 left-0 opacity-0 z-10"
                             type="checkbox"
                             type="checkbox"
                             {...register("billingSameAsShipping")}
                             {...register("billingSameAsShipping")}
@@ -192,7 +188,7 @@ export default function BillingAddressCheckout ({
                         Name *
                         Name *
                     </label>
                     </label>
                     <div className="w-full flex justify-between">
                     <div className="w-full flex justify-between">
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                             <InputText
                                 type="text"
                                 type="text"
                                 placeholder="First name"
                                 placeholder="First name"
@@ -206,7 +202,7 @@ export default function BillingAddressCheckout ({
                                 error={errors.billingFirstName && errors.billingFirstName.message as string}
                                 error={errors.billingFirstName && errors.billingFirstName.message as string}
                             />
                             />
                         </div> 
                         </div> 
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                             <InputText
                                 type="text"
                                 type="text"
                                 placeholder="Last name"
                                 placeholder="Last name"
@@ -240,7 +236,7 @@ export default function BillingAddressCheckout ({
                     <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                     <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                     <div className="w-full">
                     <div className="w-full">
                         <div className="w-full flex justify-between">
                         <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"
                                 <Select placeholder="Country/Region"
                                     {...register("billingCountry", {
                                     {...register("billingCountry", {
                                         required: "Country/Region field is required",
                                         required: "Country/Region field is required",
@@ -259,7 +255,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingCountry && errors.billingCountry.message as string}
                                     error={errors.billingCountry && errors.billingCountry.message as string}
                                 /> 
                                 /> 
                             </div>
                             </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" />}
                                 {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                                 {statesOptions.length > 0 ?
                                 {statesOptions.length > 0 ?
                                 (
                                 (
@@ -286,7 +282,7 @@ export default function BillingAddressCheckout ({
                             
                             
                         </div>
                         </div>
                         <div className="w-full flex justify-between mt-4">
                         <div className="w-full flex justify-between mt-4">
-                            <div className="w-41.25 flex-initial">
+                            <div className="w-41.25 flex-none">
                                 <InputText
                                 <InputText
                                     type="text"
                                     type="text"
                                     placeholder="Zip/Postal Code"
                                     placeholder="Zip/Postal Code"
@@ -300,7 +296,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingPostcode&& errors.billingPostcode.message as string}
                                     error={errors.billingPostcode&& errors.billingPostcode.message as string}
                                 />
                                 />
                             </div>
                             </div>
-                            <div className="w-41.25 flex-initial">
+                            <div className="w-41.25 flex-none">
                                 <InputText
                                 <InputText
                                     type="text"
                                     type="text"
                                     placeholder="City"
                                     placeholder="City"

+ 0 - 70
src/app/(checkout)/checkout/_components/CheckoutAddressModal.tsx

@@ -1,70 +0,0 @@
-"use client";
-
-import {
-  Drawer,
-  DrawerContent,
-  DrawerHeader,
-  DrawerBody,
-  DrawerFooter
-} from "@heroui/drawer";
-
-export default function CheckoutAddressModal({ 
-    isOpen,
-    onClose,
-    // onSave,
-    body,
-    footer
-}: {
-    isOpen: boolean;
-    onClose: (e: boolean) => void;
-    // onSave: () => void;
-    body: React.ReactNode;
-    footer: React.ReactNode;
-}) {
-
-    const openChange = (e: boolean) => {
-        onClose(e);
-        console.log('address modal open change ---- ', e);
-    }
-
-    return (
-   
-            <Drawer
-                backdrop={"blur"}
-                placement="bottom"
-                isDismissable={false}
-                isKeyboardDismissDisabled={true}
-                isOpen={isOpen}
-                hideCloseButton
-                onOpenChange={(e) => openChange(e)}
-            >
-                <DrawerContent className="rounded-none h-17/20 max-h-none">
-                {(onClose) => (
-                    <>
-                        <DrawerHeader className="p-0">
-                            <div className="w-full box-border p-3.75 flex justify-between items-center">
-                                <span>Shipping Address</span>
-                                <button className="w-6 h-6" onClick={onClose}>
-                                    <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>
-                        </DrawerHeader>
-                        <DrawerBody className="drawer-body relative 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>
-                                {body}
-                            </div>
-                            
-                        </DrawerBody>
-                        <DrawerFooter className="box-border border-t border-gray-200 p-3.75">
-                            {footer}
-                        </DrawerFooter>
-                    </>
-                )}
-                </DrawerContent>
-            </Drawer>
-  
-    ); 
-}

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

@@ -0,0 +1,187 @@
+"use client";
+
+import { useRouter } from 'next/navigation'
+import PaypalButton from "./PaymentButton/PaypalButton";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
+import { PlaceOrderFunction } from "@/types/checkout/type";
+import { useCustomToast } from "@/utils/hooks/useToast";
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+export default function CheckoutPlaceOrder({
+    paymentMethod,
+    clickPlaceOrder, 
+    validateCheckout,// 校验运输方式,地址 
+}: {
+    paymentMethod: string;
+    clickPlaceOrder: PlaceOrderFunction;
+    validateCheckout: () => Promise<boolean>;
+}) {
+    const router = useRouter();
+    const { showToast } = useCustomToast();
+    const { createPaymentCallback } = usePlaceOrder();
+    const paypalCreateOrder = async () => {
+        overlayLoading.start();
+        const valid = await validateCheckout();
+        if(!valid) {overlayLoading.stop();}
+        // 校验通过
+        const placeOrderRes = await clickPlaceOrder();
+        if(placeOrderRes.data !== null) {
+            return {
+                error: false,
+                msg: '',
+                orderId: placeOrderRes.data.gatewayOrderId,
+                webOrderId: placeOrderRes.data.orderId
+            };
+        } else {
+            return {
+                error: true,
+                msg: '',
+                orderId: '',
+                webOrderId: ''
+            };
+
+        }
+    };
+
+    const airwallexPlaceOrder = async () => {
+        overlayLoading.start();
+        const valid = await validateCheckout();
+        if(!valid) {overlayLoading.stop();}
+
+        const placeOrderRes = await clickPlaceOrder({
+            paymentSuccessUrl: window.location.origin + '/paymentresult/result'
+        });
+        overlayLoading.stop();
+        if(placeOrderRes.data !== null) {
+            window.location.href = placeOrderRes.data.gatewayOrderId;
+        }
+    };
+
+    const cardAirwallexPlaceOrder = async () => {
+        overlayLoading.start();
+        const valid = await validateCheckout();
+        if(!valid) {overlayLoading.stop();}
+
+        const placeOrderRes = await clickPlaceOrder();
+        if(placeOrderRes.data !== null && placeOrderRes.otherData !== null) {
+            const webOrderId = placeOrderRes.data.orderId;
+            const cardNumberElement = placeOrderRes.otherData;
+            const paymentIntentIdClientSecret = placeOrderRes.data.gatewayOrderId.split(':');
+            const paymentIntentId = paymentIntentIdClientSecret[0];
+            const paymentIntentClientSecret = paymentIntentIdClientSecret[1];
+
+            cardNumberElement.confirm({
+                intent_id: paymentIntentId,
+                client_secret: paymentIntentClientSecret,
+            }).then((result) => { 
+                overlayLoading.stop();
+                console.log('cardNumberElement.confirm result ---- ', result);
+                // 支付成功 跳转页面
+                router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=success');
+            }).catch((error) => {
+                overlayLoading.stop();
+                console.log('cardNumberElement.confirm error ---- ', error);
+                // 支付失败
+                router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=failure');
+            });
+        }
+    };
+
+    const afterpayPlaceOrder = async () => { 
+        if(window.AfterPay) {
+            overlayLoading.start();
+            const valid = await validateCheckout();
+            if(!valid) {overlayLoading.stop();} 
+
+            const placeOrderRes = await clickPlaceOrder({
+                paymentSuccessUrl: window.location.origin + '/paymentresult/result',
+                paymentCancelUrl: window.location.origin + '/paymentresult/result'
+            });
+            
+            if(placeOrderRes.data !== null) {
+                const afterpayToken = placeOrderRes.data.gatewayOrderId;
+                const webOrderId = placeOrderRes.data.orderId;
+                window.AfterPay.initialize({countryCode: "US"});
+                // To avoid triggering browser anti-popup rules, the AfterPay.open()
+                // function must be directly called inside the click event listener
+                window.AfterPay.open();
+                // If you don't already have a checkout token at this point, you can
+                // AJAX to your backend to retrieve one here. The spinning animation
+                // will continue until `AfterPay.transfer` is called.
+                // If you fail to get a token you can call AfterPay.close()
+                window.AfterPay.onComplete = async (event) => {
+                    console.log('window.AfterPay.onComplete event ---- ', event);
+                    const status: "success" | "cancel" | "failure" = event.data.status === "SUCCESS" ? 'success' : 'cancel';
+                    const resCallback = await createPaymentCallback({
+                        orderId: Number(webOrderId),
+                        gatewayOrderId: event.data.orderToken,
+                        status: status,
+                    });
+                    overlayLoading.stop();
+                    if (event.data.status == "SUCCESS") {
+                        // The customer confirmed the payment schedule.
+                        // The token is now ready to be captured from your server backend.
+                        // 跳转结果页 成功
+                        if(!resCallback.error) {
+                            // 后端capture成功,跳转到成功落地页 
+                            router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=success');
+                        } else {
+                            // capture 失败,提示错误
+                            confirmDialog({
+                                title: "Somthing Wrong",
+                                content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderId,
+                                noCancel: true,
+                            }).then(() => { 
+                                router.replace('/');
+                            });
+                            
+                        }
+                        
+                    } else {
+                        // The customer cancelled the payment or closed the popup window.
+                        // 跳转结果页 取消
+                        router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=cancel');
+                    }
+                }
+                window.AfterPay.transfer({token: afterpayToken});
+            }
+        } else {
+            showToast("Please wait for the Afterpay to load completely.","warning");
+        }
+    };
+
+    return (
+        <>
+            <div className="relative w-full">
+                {paymentMethod === 'paypal_smart_button' && 
+                    <PaypalButton
+                        createOrder={paypalCreateOrder}
+                    />
+                }
+                {(paymentMethod === 'awxklarna' || paymentMethod === 'awxafterpay') &&
+                    <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
+                        onClick={airwallexPlaceOrder}
+                    >
+                        Place Order
+                    </button>
+                }
+                {paymentMethod === 'airwallex' && 
+                    <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
+                        onClick={cardAirwallexPlaceOrder}
+                    >
+                        Place Order
+                    </button>
+                }
+                {paymentMethod === 'afterpay' &&
+                    <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
+                        onClick={afterpayPlaceOrder}
+                    >
+                        Place Order
+                    </button>
+                }
+                
+
+            </div>
+        </>
+    );
+}

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

+ 462 - 85
src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx

@@ -1,32 +1,164 @@
 "use client";
 "use client";
 
 
-import {useState, useEffect} from "react";
-
+import {useState, useRef, useEffect } from "react";
+import { Payment } from "@airwallex/components-sdk";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
 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 { 
 import { 
-    Country,
     ShipAddressFormData,
     ShipAddressFormData,
-    BillAddressFormData,
     FullAddressFormData,
     FullAddressFormData,
-    CreateCheckoutAddressVariables
+    CreateCheckoutAddressVariables,
+    ExtraMapPlaceOrderResult,
+    PlaceOrderResult,
+    PlaceOrderFunction
 } from "@/types/checkout/type";
 } from "@/types/checkout/type";
 import AddressResultDisplay from "./AddressResultDisplay";
 import AddressResultDisplay from "./AddressResultDisplay";
-import AddressResultDisplayLoading from "./AddressResultDisplayLoading";
-import CheckoutAddressModal from "./CheckoutAddressModal";
 import ShippingAddressCheckout from "./ShippingAddressCheckout";
 import ShippingAddressCheckout from "./ShippingAddressCheckout";
 import BillingAddressCheckout from "./BillingAddressCheckout";
 import BillingAddressCheckout from "./BillingAddressCheckout";
-// import CheckoutShippingMethod from "./CheckoutShippingMethod";
+import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
 import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
+import {useCheckoutPaymentMethod} from  "@/utils/hooks/useCheckoutPaymentMethod";
+import {useCheckoutShippingMethod} from  "@/utils/hooks/useCheckoutShippingMethod";
+import { useAppDispatch } from "@/store/hooks";
+import { updateCart } from "@/store/slices/cart-slice";
+import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout";
+import LoadingPaymentMethod from "./LoadingPaymentMethod";
+import LoadingCheckoutPlaceOrder from "./LoadingCheckoutPlaceOrder";
+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 { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+import { CartDetail,CartAddress } from "@/types/cart/type";
+import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import { clearCart } from "@/store/slices/cart-slice";
+import {loadAfterpayScript} from "@/lib/Afterpay/afterpay";
+import PaypalApplepayButton from "./PaymentButton/PaypalApplepayButton";
 /***
 /***
  * 不用useForShipping字段了
  * 不用useForShipping字段了
  * 以shipping address 为准,根据shippingaddress 设置billing address
  * 以shipping address 为准,根据shippingaddress 设置billing address
  * 保存完地址之后要重新获取运输方式和支付方式
  * 保存完地址之后要重新获取运输方式和支付方式
+ * 变更运输方式后重新获取支付方式
+ * @todo 购物车为空 跳转到空购物车页面
+ */
+
+/**
+ * 地址表单默认值
  */
  */
+function getBillingAddressFormData(billingAddress:CartAddress) {
+    const billingCountry = billingAddress.country || 'US';
+    const billingPhoneNumber =  normalizePhoneForForm(billingAddress.phone, billingCountry) || "";
+    const res = {
+        "billingAddressId": billingAddress.id,
+        "billingEmail": billingAddress.email,
+        "billingFirstName": billingAddress.firstName,
+        "billingLastName": billingAddress.lastName,
+        "billingCompanyName": "",
+        "billingAddress": billingAddress.address,
+        "billingCountry": billingCountry,
+        "billingState": billingAddress.state,
+        "billingCity": billingAddress.city,
+        "billingPostcode": billingAddress.postcode,
+        "billingPhoneNumber": billingPhoneNumber,
+    };
 
 
+    return res;
+}
+function getShippingAddressFormData(shippingAddress:CartAddress) {
+    const shippingCountry = shippingAddress.country || 'US';
+    const shippingPhoneNumber =  normalizePhoneForForm(shippingAddress.phone, shippingCountry) || "";
+    const res = {
+        "shippingAddressId": shippingAddress.id,
+        "shippingEmail": shippingAddress.email,
+        "shippingFirstName": shippingAddress.firstName,
+        "shippingLastName": shippingAddress.lastName,
+        "shippingCompanyName": "",
+        "shippingAddress": shippingAddress.address,
+        "shippingCountry": shippingCountry,
+        "shippingState": shippingAddress.state,
+        "shippingCity": shippingAddress.city,
+        "shippingPostcode": shippingAddress.postcode,
+        "shippingPhoneNumber": shippingPhoneNumber,
+    };
+
+    return res;
+}
+function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) {
+    const shippingAddress = cartDetail.shippingAddress;
+    const billingAddress = cartDetail.billingAddress;
+    const defaultValues = {
+        "shippingAddressId": "",
+        "shippingEmail": loginEmail ?? '',
+        "shippingFirstName": "",
+        "shippingLastName": "",
+        "shippingCompanyName": "",
+        "shippingAddress": "",
+        "shippingCountry": "US",
+        "shippingState": "",
+        "shippingCity": "",
+        "shippingPostcode": "",
+        "shippingPhoneNumber": "",
+        "billingAddressId": "",
+        "billingEmail": loginEmail ?? '',
+        "billingFirstName": "",
+        "billingLastName": "",
+        "billingCompanyName": "",
+        "billingAddress": "",
+        "billingCountry": "US",
+        "billingState": "",
+        "billingCity": "",
+        "billingPostcode": "",
+        "billingPhoneNumber": "",
+        "billingSameAsShipping": true
+    }
+    if(billingAddress) {
+        const bs = getBillingAddressFormData(billingAddress);
+        defaultValues.billingAddressId = bs.billingAddressId;
+        defaultValues.billingEmail = bs.billingEmail;
+        defaultValues.billingFirstName = bs.billingFirstName;
+        defaultValues.billingLastName = bs.billingLastName;
+        defaultValues.billingCompanyName = bs.billingCompanyName;
+        defaultValues.billingAddress = bs.billingAddress;
+        defaultValues.billingCountry = bs.billingCountry;
+        defaultValues.billingState = bs.billingState;
+        defaultValues.billingCity = bs.billingCity;
+        defaultValues.billingPostcode = bs.billingPostcode;
+        defaultValues.billingPhoneNumber = bs.billingPhoneNumber;
+        // defaultValues.billingSameAsShipping = true;
+    }
+    if(shippingAddress) {
+        const ss = getShippingAddressFormData(shippingAddress);
+        defaultValues.shippingAddressId = ss.shippingAddressId;
+        defaultValues.shippingEmail = ss.shippingEmail;
+        defaultValues.shippingFirstName = ss.shippingFirstName;
+        defaultValues.shippingLastName = ss.shippingLastName;
+        defaultValues.shippingCompanyName = ss.shippingCompanyName;
+        defaultValues.shippingAddress = ss.shippingAddress;
+        defaultValues.shippingCountry = ss.shippingCountry;
+        defaultValues.shippingState = ss.shippingState;
+        defaultValues.shippingCity = ss.shippingCity;
+        defaultValues.shippingPostcode = ss.shippingPostcode;
+        defaultValues.shippingPhoneNumber = ss.shippingPhoneNumber;
+    }
+    if( defaultValues.billingEmail === defaultValues.shippingEmail &&
+        defaultValues.billingFirstName === defaultValues.shippingFirstName &&
+        defaultValues.billingLastName === defaultValues.shippingLastName &&
+        defaultValues.billingCompanyName === defaultValues.shippingCompanyName &&
+        defaultValues.billingAddress === defaultValues.shippingAddress &&
+        defaultValues.billingCountry === defaultValues.shippingCountry &&
+        defaultValues.billingState === defaultValues.shippingState &&
+        defaultValues.billingCity === defaultValues.shippingCity &&
+        defaultValues.billingPostcode === defaultValues.shippingPostcode &&
+        defaultValues.billingPhoneNumber === defaultValues.shippingPhoneNumber
+    ) {
+        defaultValues.billingSameAsShipping = true;
+    }
+    return defaultValues;
+}
 /**
 /**
  * 生成createCheckoutAddress接口的参数
  * 生成createCheckoutAddress接口的参数
  */
  */
@@ -81,82 +213,71 @@ 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({
 export default function CheckoutWrapper({
     loginEmail,
     loginEmail,
-    countries
+    cartDetailData,
 }: {
 }: {
     loginEmail: string;
     loginEmail: string;
-    countries: Country[]
+    cartDetailData: CartDetail;
 }) {
 }) {
+    const { createOrder } = usePlaceOrder(); 
+    const addressFormDefaultValues = getAddressFormDataFromCart(cartDetailData,loginEmail);
+    const dispatch = useAppDispatch();
+    
+    const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
+    const {saveCheckoutCart} = useSaveCheckoutCart();
+    
+    const {
+        data: shippingMethodDatas,
+        loading: shippingMethodLoading,
+        error: shippingMethodError,
+        getShippingMethod,
+    } = useCheckoutShippingMethod();
+
+
+    const selectedPaymentMethod = cartData.paymentMethod || 'paypal_smart_button';
+    const selectedShippingRate = cartData.selectedShippingRate;
+   
+    const { 
+        data: paymentMethodDatas,
+        loading: paymentMethodLoading,
+        error: paymentMethodError,
+        getPaymentMethod, 
+    } = useCheckoutPaymentMethod();
+
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
     const { getCheckoutAddress, saveCheckoutAddress } = useCheckoutAddress(loginEmail);
     const { getCheckoutAddress, saveCheckoutAddress } = useCheckoutAddress(loginEmail);
 
 
+    const methodShiippingRef = useRef<RefShippingMethodsHandle>(null);
+    const methodPaymentRef = useRef<RefPaymentMethodsHandle>(null);
+
+    
 
 
     // useForm() 只会在组件初始化时读取一次 defaultValues
     // useForm() 只会在组件初始化时读取一次 defaultValues
     const addressForm = useForm<FullAddressFormData>({
     const addressForm = useForm<FullAddressFormData>({
-        mode: "onBlur",          // 或 "onChange"
-        reValidateMode: "onChange", 
+        mode: "onChange",          // 或 "onChange"
+        reValidateMode: "onChange",
+        defaultValues: addressFormDefaultValues 
     });
     });
 
 
     const [addressModalOpen, setAddressModalOpen] = useState(false);
     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 [addressSaving, setAddressSaving] = useState(false);
 
 
-    // 开发环境会执行两次 https://zh-hans.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
-    useEffect(() => { 
-        getCheckoutAddress((shippingAddress,billingAddress)=> {
-            addressForm.reset({
-                ...shippingAddress,
-                ...billingAddress,
-            });
-            setServerShippingAddress({
-                ...shippingAddress
-            });
-            setServerBillingAddress({
-                ...billingAddress
-            });
-            setLoadingData(false);
-        }, ()=> {
-            setLoadingData(false);
-        });
-    }, []);
+    const [addressSaving, setAddressSaving] = useState(false); // 保存地址时的loading状态
+
+
 
 
 
 
     const openAddressModal = () => {
     const openAddressModal = () => {
         setAddressModalOpen(true);
         setAddressModalOpen(true);
 
 
-        if(serverShippingAddress && serverBillingAddress) {
+        if(cartData.billingAddress && cartData.shippingAddress) {
             addressForm.reset({
             addressForm.reset({
-                ...serverShippingAddress,
-                ...serverBillingAddress,
+                ...getAddressFormDataFromCart(cartData)
             });
             });
         }
         }
+
         
         
     };
     };
     const closeAddressModal = (e:boolean) => {
     const closeAddressModal = (e:boolean) => {
@@ -166,14 +287,20 @@ export default function CheckoutWrapper({
     };
     };
     const addressFormOnSubmit = async (formData: FullAddressFormData) => { 
     const addressFormOnSubmit = async (formData: FullAddressFormData) => { 
         // console.log('addressFormOnSubmit ---- ',formData); return;
         // console.log('addressFormOnSubmit ---- ',formData); return;
-        // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;还需要获取运输方式
-
+        if(addressSaving) {
+            return;
+        }
         const saveAddressParam = generateSaveCheckoutAddressParam(formData);
         const saveAddressParam = generateSaveCheckoutAddressParam(formData);
         setAddressSaving(true);
         setAddressSaving(true);
         try {
         try {
 
 
             const res = await saveCheckoutAddress(saveAddressParam);
             const res = await saveCheckoutAddress(saveAddressParam);
             console.log('CREATE_CHECKOUT_ADDRESS res ====== ',res);
             console.log('CREATE_CHECKOUT_ADDRESS res ====== ',res);
+            // 地址保存成功后重新获取运输方式和支付方式 
+            await getShippingMethod();
+            await getPaymentMethod();
+            
+            // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;
             const queryAddressRes = await getCheckoutAddress();
             const queryAddressRes = await getCheckoutAddress();
             if(queryAddressRes !== null) {
             if(queryAddressRes !== null) {
                 addressForm.resetField('shippingAddressId',{
                 addressForm.resetField('shippingAddressId',{
@@ -182,12 +309,20 @@ export default function CheckoutWrapper({
                 addressForm.resetField('billingAddressId',{
                 addressForm.resetField('billingAddressId',{
                     defaultValue: queryAddressRes.billingAddress.billingAddressId
                     defaultValue: queryAddressRes.billingAddress.billingAddressId
                 });
                 });
-                setServerShippingAddress({
-                    ...queryAddressRes.shippingAddress
-                });
-                setServerBillingAddress({
-                    ...queryAddressRes.billingAddress
-                });
+
+      
+                // 同步地址到购物车详情
+                const newBillingAddress = billingAddressToCartAddress(queryAddressRes.billingAddress);
+                const newShippingAddress = shippingAddressToCartAddress(queryAddressRes.shippingAddress);
+                dispatch(updateCart({
+                    billingAddress: newBillingAddress,
+                    shippingAddress: newShippingAddress
+                }));
+                setCartData(Object.assign({...cartData},{
+                    billingAddress: {...newBillingAddress},
+                    shippingAddress: {...newShippingAddress}
+                }));
+
             }
             }
             setAddressModalOpen(false);
             setAddressModalOpen(false);
         } catch(err) {
         } catch(err) {
@@ -200,6 +335,175 @@ export default function CheckoutWrapper({
         }
         }
         
         
     };
     };
+
+
+    const handlePaymentMethodChange =  async (method: 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));
+                setCartData({...newCartDetail});
+            }
+            return true;
+        } else {
+            showToast(saveRes.msg, 'danger');
+            return false;
+        }
+
+    };
+    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));
+                setCartData({...newCartDetail});
+            }
+            // 保存成功之后需要重新获取支付方式
+            getPaymentMethod();
+        } else {
+            showToast(saveRes.msg, 'danger');
+        }
+
+    };
+
+
+    // 校验地址 运输方式,支付方式;
+    const validateCheckout = 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();
+            const paymentMethod = methodPaymentRef.current.getSelectPaymentMethod();
+            if(!paymentMethodValid) {
+                showToast('Please select a payment method.', 'danger');
+                return false;
+            }
+            if(paymentMethod === 'airwallex') {
+                const paymentMethodValid = methodPaymentRef.current.validateAirwallexCard();
+                return paymentMethodValid;
+            }
+
+        } 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));
+                    setCartData({...newCartDetail});
+                }
+            }
+        }
+
+        return true;
+
+    };
+    // 苹果支付下单
+    const applepayPlaceOrder = async () => { 
+        const res = await createOrder();
+        if(!res.error) {
+            //创建订单成功后清空redux 购物车
+            dispatch(clearCart());  
+            return {
+                orderId: res.data?.gatewayOrderId ?? '',
+                webOrderId: res.data?.orderId ?? ''
+            };
+        } else{
+            // 抛出错误让第三方支付能够捕获
+            throw new Error("CREATE_ORDER_ERROR: " + res.msg);
+        } 
+    };
+    // 下单
+    const handlePlaceOrder: PlaceOrderFunction = async (params) => { 
+        // 校验地址
+        // overlayLoading.start();
+        // const validRes = await validateCheckout();
+        // if(validRes) {
+            const res = await createOrder(params);
+            if(res.error) {
+                overlayLoading.stop();
+                confirmDialog({
+                    title: "Warning",
+                    content: res.msg + " Create order failed. Please try again.",
+                    noCancel: true,
+                }).then(() => {});
+            } else {
+                //创建订单成功后清空redux 购物车
+                dispatch(clearCart());  
+            }
+            const pmethod = methodPaymentRef.current?.getSelectPaymentMethod();
+            const resultData:PlaceOrderResult<keyof ExtraMapPlaceOrderResult> = {
+                // valid: true, 
+                data: res.data,
+                otherData: null
+            }
+            if(pmethod === 'airwallex' && methodPaymentRef.current) {
+                const airwallexCardElements = methodPaymentRef.current.getAirwallexCardElements();
+                resultData.otherData = airwallexCardElements.cardNumber as Payment.CardNumberElementType;
+            }
+            return resultData;
+        // } else {
+        //     overlayLoading.stop();
+        //     return {valid: false, data: null, otherData: null};
+        // }
+    };                    
+    
+    useEffect(() => {
+        const hasAfterpay = paymentMethodDatas.some(x=>x.method==="afterpay");
+
+        if(hasAfterpay){
+            loadAfterpayScript();
+        }
+    }, [paymentMethodDatas]);
+
+
     // function tt() {
     // function tt() {
     //     const arr = [
     //     const arr = [
     //         ['US','3803800217'], 
     //         ['US','3803800217'], 
@@ -223,36 +527,109 @@ export default function CheckoutWrapper({
         <section className="w-full">
         <section className="w-full">
             <div>
             <div>
             {/* <button className="w-25 h-8 bg-amber-500 flex items-center justify-center" onClick={tt}>test</button> */}
             {/* <button className="w-25 h-8 bg-amber-500 flex items-center justify-center" onClick={tt}>test</button> */}
-                    {loadingData ? <AddressResultDisplayLoading /> 
-                    :<AddressResultDisplay shippingAddress={serverShippingAddress} 
+                    {/* {loadingAddress ? 
+                        <AddressResultDisplayLoading /> 
+                    :
+                        <AddressResultDisplay shippingAddress={cartData.shippingAddress || null} 
+                            onAddressModalOpenClick={openAddressModal}
+                        />
+                    } */}
+                    <AddressResultDisplay shippingAddress={cartData.shippingAddress || null} 
                         onAddressModalOpenClick={openAddressModal}
                         onAddressModalOpenClick={openAddressModal}
-                    />}
-                    <CheckoutAddressModal 
+                    />
+                    <CommonModal
                         isOpen={addressModalOpen}
                         isOpen={addressModalOpen}
                         onClose={closeAddressModal}
                         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={
                         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 />
+                                            <BillingAddressCheckout />
+                                        </form>
+                                    </FormProvider>
+                                </div>
+                            </div>
                         }
                         }
                         footer={
                         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"
                             <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)}
                                 onClick={addressForm.handleSubmit(addressFormOnSubmit)}
                             >
                             >
                                 {addressSaving ? <LoadingSpinner /> : 'Save'}
                                 {addressSaving ? <LoadingSpinner /> : 'Save'}
                             </button>
                             </button>
+                            </div>
                         }
                         }
                     />
                     />
             </div>
             </div>
             <div className="mt-6 box-border px-4">
             <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}  
+                        selectedShippingRate={selectedShippingRate}
+                        onShipMethodChange={handleShippingMethodChange}  
+                    />
+                }
+
             </div>
             </div>
-           
+
+            <div className="mt-6 box-border px-4">
+                <h3 className="text-ly-24 font-medium">Payment Method</h3>
+                { paymentMethodLoading && <LoadingPaymentMethod /> }
+                { (!paymentMethodLoading && paymentMethodError) && <p className="mt-3 text-red-500 text-ly-12">{paymentMethodError}</p>}
+                { (!paymentMethodLoading && !paymentMethodError) && 
+                    <PaymentMethodCheckout 
+                        ref={methodPaymentRef}
+                        paymentMethods={paymentMethodDatas} 
+                        selectedPaymentMethod={selectedPaymentMethod}
+                        onPaymentMethodChange={handlePaymentMethodChange}
+                    />
+                }
+                
+            </div>
+            <div className="mt-3 box-border px-4 pb-8">
+                <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">
+                    { paymentMethodLoading ?
+                        <LoadingCheckoutPlaceOrder />
+                    :
+                        (selectedPaymentMethod === 'applepay' ?
+                        <PaypalApplepayButton 
+                            grandTotal={cartData.grandTotal}
+                            billingContact={cartData.billingAddress}
+                            shippingContact={cartData.shippingAddress}
+                            validateCheckout={validateCheckout}
+                            createOrder={applepayPlaceOrder}
+                        />
+                        :
+                        <CheckoutPlaceOrder 
+                            paymentMethod={selectedPaymentMethod}
+                            validateCheckout={validateCheckout}
+                            clickPlaceOrder={handlePlaceOrder}
+                        />)
+                        
+                    }
+                </div>
+            </div>
+
         </section> 
         </section> 
         
         
     );
     );

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 195 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 409 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx


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

@@ -0,0 +1,120 @@
+"use client";
+
+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>
+      }
+      
+    />
+  )
+}

+ 12 - 0
src/app/(checkout)/checkout/_components/LoadingCheckoutPlaceOrder.tsx

@@ -0,0 +1,12 @@
+export default function LoadingCheckoutPlaceOrder() { 
+    return (
+       
+        <div className="relative w-full animate-pulse">
+                        
+            <button className="flex items-center justify-center w-full h-12 bg-gray-300 rounded-3xl text-ly-16 font-bold"></button>
+                        
+        
+        </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>
+    );
+}

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

@@ -0,0 +1,301 @@
+"use client";
+
+
+import {useRef,useEffect,useCallback} from "react";
+import { useRouter } from 'next/navigation';
+// import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import {
+  usePayPal,
+  useEligibleMethods,
+  INSTANCE_LOADING_STATE,
+  useApplePayOneTimePaymentSession,
+  type ConfirmOrderResponse,
+  type UseApplePayOneTimePaymentSessionProps,
+} from "@paypal/react-paypal-js/sdk-v6";
+import { useAppSelector } from "@/store/hooks";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
+import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+import { CartAddress } from "@/types/cart/type";
+import { useConfig } from "@/utils/hooks/useConfig";
+import {selectCurrentCurrency} from "@/store/selectors/currentConfig";
+import {ApplePayAddressContact} from "@/lib/Applepay/type";
+// import {getPaypalApplePayConfig, type PaypalApplePaySession} from "@/lib/Applepay/applepay";
+
+export default function PaypalApplepayButton({
+    createOrder,
+    validateCheckout,
+    billingContact,
+    shippingContact,
+    grandTotal,
+    isRePay
+}:{
+    validateCheckout: () => Promise<boolean>;
+    createOrder: () => Promise<{orderId: string;webOrderId: string;}>;
+    billingContact: CartAddress | null;
+    shippingContact: CartAddress | null;
+    grandTotal: number;
+    isRePay?: boolean;
+}) { 
+    const router = useRouter();
+    // const {createPaymentCallback} = usePlaceOrder();
+    const {countries,store} = useConfig();
+
+    const currentCurrency = useAppSelector(selectCurrentCurrency);
+    const currencyCode = currentCurrency.code;
+    
+    const storeName = store.storeName;
+    const gatewayOrderIdRef = useRef("");
+    const webOrderIdRef = useRef("");
+    // const [configCountryCode, setConfigCountryCode] = useState('');
+
+    // paypal sdk 是否加载完成
+    const { loadingStatus, isHydrated } = usePayPal();
+    // Fetch eligibility(资格) for one-time payment flow
+
+
+    const {
+        error: eligibilityError,
+        eligiblePaymentMethods,
+        isLoading: isEligibilityLoading,
+    } = useEligibleMethods({
+        payload: {
+            currencyCode: currencyCode, // 需要根据用户选择的货币走
+            paymentFlow: "ONE_TIME_PAYMENT", // 一次性支付
+        },
+    });
+    const isLoading = loadingStatus === INSTANCE_LOADING_STATE.PENDING;
+    const isApplepayEligible =  eligiblePaymentMethods?.isEligible("applepay");
+    const applePayConfig = eligiblePaymentMethods?.getDetails("applepay");
+
+    
+
+    // 支付回调配置
+
+    /**
+     * when the user has authorized the Apple Pay payment with Touch ID, Face ID, or a passcode.
+     * ApplePaySession onpaymentauthorized 时触发 */
+    const configCreateOrder =  useCallback(async () => {
+        const res = await createOrder();
+        gatewayOrderIdRef.current = res.orderId;
+        webOrderIdRef.current = res.webOrderId;
+        return res;
+    },[createOrder]);
+
+    /**await paypal.Applepay().confirmOrder() 后调onApprove */
+    const onApprove = useCallback((data: ConfirmOrderResponse) => {
+        console.log("applepay approved:", data);
+        /**
+         * approveApplePayPayment: {
+         *     id: string; //gatewayOrderId
+         *     status: "APPROVED"
+         * }
+         */
+        overlayLoading.stop();
+        router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=success');
+
+    },[router]);
+    /**ApplePaySession oncancel */
+    const onCancel = useCallback(() => {
+        console.log("applepay cancelled:");
+        confirmDialog({
+            title: "Payment cancelled",
+            content: "You canceled the payment.",
+            noCancel: true,
+        }).then(() => {  });
+        
+    },[]);
+
+    /**
+     * 1. ApplePaySession onvalidatemerchant 出错
+     * 2. ApplePaySession completePayment 出错
+     * 3. createOrder->paypal.Applepay().confirmOrder()->onApprove() 链条出错
+     * 4. 用户点击后的整个链条出错
+     */
+    const onError = useCallback((error: Error) => {
+        
+        console.error("applepay error:", error);
+        if(!isRePay) {
+            overlayLoading.stop();
+            if(webOrderIdRef.current || gatewayOrderIdRef.current) {
+                // 订单创建成功了,跳转二次支付页面
+                confirmDialog({
+                    title: "Payment failed",
+                    content: error.message,
+                    noCancel: true,
+                }).then(() => {  
+                    router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=failure');
+                });
+                
+            } else {
+                // 订单创建失败,留在当前页面
+                confirmDialog({
+                    title: "Payment failed",
+                    content: error.message + " Please try again.",
+                    noCancel: true,
+                }).then(() => { });
+            }
+
+        } else {
+            
+            // 登录用户 二次支付页面 
+            confirmDialog({
+                title: "Payment Failed",
+                content: error.message,
+                noCancel: true,
+            }).then(() => {  });
+        
+        }
+    },[router,isRePay]);
+    
+
+    const addressContact: ApplePayAddressContact = {};
+    if(billingContact) {
+        const billCountry = countries.find((item) => item.code === billingContact.country);
+        addressContact.billingContact = {
+            addressLines: [billingContact.address],
+            administrativeArea: billingContact.state,
+            country: billCountry?.name,
+            countryCode: billingContact.country,
+            emailAddress: billingContact.email,
+            familyName: billingContact.lastName,
+            givenName: billingContact.firstName,
+            locality: billingContact.city,
+            postalCode:billingContact.postcode,
+        }
+    }
+    if(shippingContact) {
+        const shipCountry = countries.find((item) => item.code === shippingContact.country);
+        addressContact.shippingContact = {
+            addressLines: [shippingContact.address],
+            administrativeArea: shippingContact.state,
+            country: shipCountry?.name,
+            countryCode: shippingContact.country,
+            emailAddress: shippingContact.email,
+            familyName: shippingContact.lastName,
+            givenName: shippingContact.firstName,
+            locality: shippingContact.city,
+            postalCode:shippingContact.postcode,
+        }
+    }
+    console.log('applePayConfig ---- ', applePayConfig?.config);
+    /*
+    useEffect(() => { 
+        if(!sdkInstance || !isHydrated) return;
+        const getConfig = async () => {
+            const paypalSdkApplePayPaymentSession = await sdkInstance.createApplePayOneTimePaymentSession() as PaypalApplePaySession;
+            const res = await getPaypalApplePayConfig(paypalSdkApplePayPaymentSession);
+            console.log('res ---- ', res);
+            setConfigCountryCode(res.countryCode);
+
+        };
+        getConfig();
+    }, [isHydrated, sdkInstance]);
+    */
+   const merchantCountry = applePayConfig?.config.merchantCountry;
+
+    if(!isApplepayEligible) { 
+        return <p className="text-ly-12 text-ly-errorcolor text-center">Applepay is not available on your device.</p>;
+    }
+    
+    return (
+        <div className="box-border w-full">
+            {isLoading || !isHydrated || isEligibilityLoading || applePayConfig === undefined ?
+                <p className="text-ly-12 text-center">Applepay is loading...</p>
+            :
+                <ApplePayButtonBox 
+                    onClickHandler={validateCheckout}
+                    eligibilityError={eligibilityError}
+                    applePayConfig={applePayConfig.config}
+                    applePaySessionVersion={4}
+                    paymentRequest={{
+                        countryCode: merchantCountry === 'C2' ? 'CN' : merchantCountry as string,
+                        currencyCode: currencyCode,
+                        requiredBillingContactFields: ["postalAddress"],
+                        requiredShippingContactFields: ["name", "email", "postalAddress"],
+                        total: {
+                            label: storeName,
+                            amount: String(grandTotal),
+                            type: "final",
+                        },
+                        ...addressContact
+                    }}
+                    createOrder={configCreateOrder}
+                    onApprove={onApprove}
+                    onCancel={onCancel}
+                    onError={onError}
+                />
+            } 
+        </div>
+    );
+}
+
+// ApplePayButtonBox是 @paypal/react-paypal-js/sdk-v6 中的<ApplePayOneTimePaymentButton>组件的代码复制过来的
+function ApplePayButtonBox({
+    eligibilityError,
+    onClickHandler, // 点击苹果支付按钮
+    ...hookProps
+}: {
+    eligibilityError: Error | null;
+    onClickHandler: () => Promise<boolean>;
+} & UseApplePayOneTimePaymentSessionProps
+) {
+    const { error, handleClick, handleDestroy } = useApplePayOneTimePaymentSession(hookProps);
+
+    const buttonRef = useRef<HTMLElement>(null);
+    const handleClickRef = useRef(handleClick);
+    // handleClickRef.current = handleClick;
+
+    useEffect(() => { 
+        handleClickRef.current = handleClick;
+    }, [handleClick]);
+
+    // Apple's <apple-pay-button> manages its own enabled/disabled state internally
+    // via canMakePayments(); we deliberately don't add an SDK-level disabled layer
+    // (merchants control presentation themselves). React's onClick also doesn't
+    // work on the element due to its shadow DOM, so we attach the handler directly.
+    useEffect(() => {
+        const el = buttonRef.current;
+        if (!el) { return; }
+        const applepayButtonClick = () => {
+            onClickHandler().then((res) => {
+                if(res) {
+                    handleClickRef.current().catch(() => {
+                        // Errors are captured by the hook's setError
+                    });
+                }
+            }).catch((err) => {
+                console.error(err);
+            });
+            
+        };
+
+        el.addEventListener("click", applepayButtonClick);
+        return () => el.removeEventListener("click", applepayButtonClick);
+    }, [onClickHandler]);
+
+    useEffect(() => {
+        if (error) {
+            console.error(error);
+        }
+    }, [error]);
+
+    // Cleanup on unmount
+    useEffect(() => {
+        return () => {
+            handleDestroy();
+        };
+    }, [handleDestroy]);
+
+
+    return (
+        <>
+            <apple-pay-button
+                ref={buttonRef}
+                buttonstyle="black"
+                type="pay"
+                locale="en"
+            />
+            { eligibilityError && <p className="text-ly-errorcolor text-ly-12 text-center">{eligibilityError.message}</p> }
+        </>
+    );
+};

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

@@ -0,0 +1,250 @@
+"use client";
+
+
+import {useRef,useState} from "react";
+import { useRouter } from 'next/navigation';
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+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";
+
+
+export default function PaypalButton({
+    createOrder,
+    isRePay
+}:{
+  createOrder: () => Promise<{orderId: string;webOrderId: string;error: boolean;msg: string;}>;
+  isRePay?: boolean;
+}) { 
+    const router = useRouter();
+
+    const {createPaymentCallback} = usePlaceOrder();
+
+    const [isPaying, setIsPaying] = useState(false);
+
+    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", // @todo 需要根据用户选择的货币走
+            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;
+    }>) => {
+
+            setIsPaying(true);
+            const res = await createOrder();
+            if(!res.error) {
+                gatewayOrderIdRef.current = res.orderId;
+                webOrderIdRef.current = res.webOrderId;
+                return buttonHandlerClick();
+            } else {
+                setIsPaying(false);
+            }
+            
+    }
+
+    // paypal支付回调配置
+    const paypalOnHandlerConfig = {
+        createOrder: async () => {
+            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',
+            });
+            overlayLoading.stop();
+            if(!resCallback.error) {
+                // 支付成功,跳转到成功落地页 
+                confirmDialog({
+                    title: "Payment Success",
+                    content: "Your payment was successful. Will redirect to success page.",
+                    noCancel: true,
+                }).then(() => {
+                    router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=success');
+                });
+            } else {
+                // callback 失败,提示错误
+                confirmDialog({
+                    title: "Somthing Wrong",
+                    content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderIdRef.current,
+                    noCancel: true,
+                }).then(() => { 
+                    router.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) {
+                overlayLoading.stop();
+                router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=cancel');
+                    
+
+            } else {
+                // 登录用户在二次支付页面 
+                confirmDialog({
+                    title: "Payment cancelled",
+                    content: "You canceled the payment.",
+                    noCancel: true,
+                }).then(() => {  });
+            }
+        },
+
+        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) {
+                overlayLoading.stop();
+                router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=failure');
+
+            } else {
+                
+                // 登录用户 二次支付页面 
+                confirmDialog({
+                    title: "Payment Failed",
+                    content: data.message,
+                    noCancel: true,
+                }).then(() => {  });
+            
+            }
+        
+        },
+
+        onComplete: (data: OnCompleteData) => {
+            overlayLoading.stop();
+            setIsPaying(false);
+            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" disabled={isPaying} />
+                        }
+                        {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 disabled={isPaying} ref={guestButtonRef} onClick={() => handlerPaypalClick(guestButtonHandleClick)} />
+                            </paypal-basic-card-container>
+                        }
+                     
+                        {isPayLaterEligible && 
+                            <paypal-pay-later-button className="w-full mt-3" disabled={isPaying}
+                                onClick={() => handlerPaypalClick(paylaterButtonHandleClick)}
+                                countryCode={payLaterDetails?.countryCode}
+                                productCode={payLaterDetails?.productCode}
+                            />
+                        }
+                        
+                    </>
+                )
+            }
+  
+        </div>
+    );
+}

+ 41 - 0
src/app/(checkout)/checkout/_components/PaymentMethodAdditional/AirwallexCardInput.tsx

@@ -0,0 +1,41 @@
+"use client";
+
+export default function AirwallexCardInput({ 
+    cardNumberErrorMsg,
+    expiryErrorMsg,
+    cvcErrorMsg,
+} : {
+    cardNumberErrorMsg: string;
+    expiryErrorMsg: string;
+    cvcErrorMsg: string;
+}) {
+
+    return (
+
+        <div id="airwallex-dropIn-card-container" className="w-full">
+            <div className="w-full">
+                <div id="airwallex_cardNumber" className="w-full border-1 border-ly-inputborder h-11.5"></div>
+                {cardNumberErrorMsg && <p className="text-ly-12 text-ly-errorcolor">{cardNumberErrorMsg}</p>}
+            </div>
+            <div className="mt-3 flex justify-between gap-4">
+                <div className="w-full">
+                    <div id="airwallex_expiry" className="w-full border-1 border-ly-inputborder h-11.5"></div>
+                    {expiryErrorMsg && <p className="text-ly-12 text-ly-errorcolor">{expiryErrorMsg}</p>}
+                </div>
+                <div className="w-full">
+                    <div id="airwallex_cvc" className="w-full border-1 border-ly-inputborder h-11.5"></div>
+                    {cvcErrorMsg && <p className="text-ly-12 text-ly-errorcolor">{cvcErrorMsg}</p>}
+                </div>
+            </div>
+           
+            <div className="mt-2 w-full flex items-center">
+                <svg className="flex-none w-4.5 h-4.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="1.4em" height="1.4em" viewBox="0 0 24 24" id="pci-svg">
+                    <path d="m18,8l-1,0l0,-2c0,-2.76 -2.24,-5 -5,-5s-5,2.24 -5,5l0,2l-1,0c-1.1,0 -2,0.9 -2,2l0,10c0,1.1 0.9,2 2,2l12,0c1.1,0 2,-0.9 2,-2l0,-10c0,-1.1 -0.9,-2 -2,-2zm-6,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2s-0.9,2 -2,2zm3.1,-9l-6.2,0l0,-2c0,-1.71 1.39,-3.1 3.1,-3.1c1.71,0 3.1,0.92125 3.1,2.84372l0,-0.78122l0,3.0375z"></path>
+                </svg>
+                <p className="text-ly-12 leading-ly-18">
+                    Your card details are protected using PCI DSS v3.2 security standards.      
+                </p>
+            </div>
+        </div>
+    )
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 195 - 0
src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx


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

@@ -0,0 +1,15 @@
+"use client";
+
+
+
+
+// 金额明细
+export default function PromotionsDetails() { 
+
+
+    return (
+        <div className="">
+
+        </div>
+    );
+}

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

@@ -13,17 +13,14 @@ import {
 } from "@/components/theme/ui/PhoneNumberInput/phoneCodeMetaData";
 } from "@/components/theme/ui/PhoneNumberInput/phoneCodeMetaData";
 import { GET_COUNTRY_STATES } from "@/graphql";
 import { GET_COUNTRY_STATES } from "@/graphql";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
-import { Country } from "@/types/checkout/type";
 import InputText from "@/components/theme/ui/InputText";
 import InputText from "@/components/theme/ui/InputText";
 import Select from "@/components/theme/ui/Select";
 import Select from "@/components/theme/ui/Select";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
+import { useConfig } from "@utils/hooks/useConfig";
 
 
-export default function ShippingAddressCheckout ({
-    countries,
-}:{
-    countries: Country[];
-}) {
+export default function ShippingAddressCheckout () {
+    const {countries} = useConfig();
     const { 
     const { 
         getValues,
         getValues,
         setValue,
         setValue,
@@ -114,7 +111,7 @@ export default function ShippingAddressCheckout ({
                     Name *
                     Name *
                 </label>
                 </label>
                 <div className="w-full flex justify-between">
                 <div className="w-full flex justify-between">
-                    <div className="w-41.25 flex-initial">
+                    <div className="w-41.25 flex-none">
                         <InputText
                         <InputText
                             type="text"
                             type="text"
                             placeholder="First name"
                             placeholder="First name"
@@ -128,7 +125,7 @@ export default function ShippingAddressCheckout ({
                             error={errors.shippingFirstName && errors.shippingFirstName.message as string}
                             error={errors.shippingFirstName && errors.shippingFirstName.message as string}
                         />
                         />
                     </div> 
                     </div> 
-                    <div className="w-41.25 flex-initial">
+                    <div className="w-41.25 flex-none">
                         <InputText
                         <InputText
                             type="text"
                             type="text"
                             placeholder="Last name"
                             placeholder="Last name"
@@ -162,7 +159,7 @@ export default function ShippingAddressCheckout ({
                 <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                 <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                 <div className="w-full">
                 <div className="w-full">
                     <div className="w-full flex justify-between">
                     <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"
                             <Select placeholder="Country/Region"
                                 {...register("shippingCountry", {
                                 {...register("shippingCountry", {
                                     required: "Country/Region field is required",
                                     required: "Country/Region field is required",
@@ -182,7 +179,7 @@ export default function ShippingAddressCheckout ({
                                 error={errors.shippingCountry && errors.shippingCountry.message as string}
                                 error={errors.shippingCountry && errors.shippingCountry.message as string}
                             /> 
                             /> 
                         </div>
                         </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" />}
                             {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                             {statesOptions.length > 0 ?
                             {statesOptions.length > 0 ?
                             (
                             (
@@ -209,7 +206,7 @@ export default function ShippingAddressCheckout ({
                         
                         
                     </div>
                     </div>
                     <div className="w-full flex justify-between mt-4">
                     <div className="w-full flex justify-between mt-4">
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                             <InputText
                                 type="text"
                                 type="text"
                                 placeholder="Zip/Postal Code"
                                 placeholder="Zip/Postal Code"
@@ -223,7 +220,7 @@ export default function ShippingAddressCheckout ({
                                 error={errors.shippingPostcode&& errors.shippingPostcode.message as string}
                                 error={errors.shippingPostcode&& errors.shippingPostcode.message as string}
                             />
                             />
                         </div>
                         </div>
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                             <InputText
                                 type="text"
                                 type="text"
                                 placeholder="City"
                                 placeholder="City"

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 136 - 0
src/app/(checkout)/checkout/_components/ShippingMethodCheckout.tsx


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

@@ -0,0 +1,87 @@
+import {Suspense} from "react";
+import { getServerSession } from "next-auth";
+import { redirect, RedirectType } from 'next/navigation';
+import { authOptions } from "@utils/auth";
+import {serverGraphqlFetch} from "@utils/bagisto/index";
+import ContinueToPayOrderInfo from "../_components/ContinueToPay/ContinueToPayOrderInfo";
+import { GET_ORDER_DETAILS, GET_REPAYORDER_PAYMENT_METHODS } from "@/graphql";
+// import { Country } from "@/types/checkout/type";
+import { OrderDetailsData } from "@/types/customer/type";
+import { RepayOrderPaymentMethodsData } from "@/types/checkout/type";
+import {PaymentMethodContinueTpPay} from "../_components/ContinueToPay/PaymentMethodContinueTpPay";
+import LoadingPaymentMethod from "../_components/LoadingPaymentMethod";
+
+// 只能存在一个待支付订单
+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 {data: orderDetailResponse} = await serverGraphqlFetch<OrderDetailsData,{id:string}>({
+        query: GET_ORDER_DETAILS,
+        variables: {
+            id: orderid as string
+        }
+    }); 
+    const orderDetailData = orderDetailResponse.customerOrder;
+    if(!orderDetailData  || orderDetailData?.status !== "pending")  {
+        redirect('/', RedirectType.replace);
+    }
+
+    // 获取支付方式列表
+    const paymentMethodsResponse = serverGraphqlFetch<RepayOrderPaymentMethodsData,{orderId:number}>({
+        query: GET_REPAYORDER_PAYMENT_METHODS,
+        variables: {
+            orderId:  Number(orderid)
+        }
+    });
+      
+
+    return (
+        <div className="w-full pb-8">
+        
+            <ContinueToPayOrderInfo orderDetail={orderDetailData} />
+           
+            <div className="mt-6 box-border px-4">
+                <h3 className="text-ly-24 font-medium">Payment Method</h3>
+
+            </div>
+            <Suspense fallback={<LoadingPaymentMethod/>}> 
+                <PaymentMethodContinueTpPay 
+                    orderId={orderDetailData._id}
+                    orderDetail={orderDetailData}
+                    paymentMethodsDataPromise={paymentMethodsResponse}
+                    defaultPaymentMethod={orderDetailData.payment}
+                />
+            </Suspense>
+            
+        </div>
+    );
+  
+}

+ 18 - 30
src/app/(checkout)/checkout/page.tsx

@@ -1,10 +1,11 @@
 import { getServerSession } from "next-auth";
 import { getServerSession } from "next-auth";
 import { authOptions } from "@utils/auth";
 import { authOptions } from "@utils/auth";
-import { fullCacheGraphqlRequest} from "@/lib/graphql-fetch";
-import CheckOut from "@/components/checkout";
+import { redirect, RedirectType } from 'next/navigation';
+import {serverGraphqlFetch} from "@utils/bagisto/index";
 import CheckoutWrapper from "./_components/CheckoutWrapper";
 import CheckoutWrapper from "./_components/CheckoutWrapper";
-import { GET_COUNTRIES } from "@/graphql";
-import { Country } from "@/types/checkout/type";
+import { GET_CART_ITEM } from "@/graphql";
+
+import { GetCartItemData } from "@/types/cart/type";
 
 
 /**
 /**
  * 进入结账页 
  * 进入结账页 
@@ -16,40 +17,27 @@ import { Country } from "@/types/checkout/type";
  *  
  *  
  * 
  * 
  */
  */
+export const dynamic = "force-dynamic";
 
 
+export default async function CheckoutPage() {
 
 
-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();
-
-    const session = await getServerSession(authOptions);
+    const session = await getServerSession(authOptions); // 游客是null
 
 
-    const {data: countriesData} = await fullCacheGraphqlRequest<{
-        countries: Country[] 
-    }>(GET_COUNTRIES);
 
 
-
-  
+    const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({
+        query: GET_CART_ITEM,
+        operationName:'GetCartItem'
+    });
+    if(cartDetailsRes.createReadCart === null) {
+        redirect('/', RedirectType.replace);
+    }
+    const cartDetails = cartDetailsRes.createReadCart.readCart;
     return (
     return (
         <>
         <>
-            {/* {addressError && <p className="text-ly-errorcolor text-ly-12">{addressError}</p>} */}
-            <CheckoutWrapper 
+            <CheckoutWrapper key={cartDetails.id}
+                cartDetailData={cartDetails}
                 loginEmail={session?.user?.email || ""}
                 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";
 import { ReactNode } from "react";
 export default async function RootLayout({
 export default async function RootLayout({
   children,
   children,
@@ -7,12 +7,12 @@ export default async function RootLayout({
 }) {
 }) {
   return (
   return (
     <>
     <>
-      <div className="block lg:hidden">
-        <Navbar />
-      </div>
-      <main className="mx-auto w-full">
+
+      <TitleHeader title="Checkout" />
+
+      <div className="mx-auto w-full">
         {children}
         {children}
-      </main>
+      </div>
     </>
     </>
   );
   );
 }
 }

+ 101 - 0
src/app/(public)/paymentresult/_components/CancelOrFailure.tsx

@@ -0,0 +1,101 @@
+"use client";
+
+import {use, useEffect, useRef} from "react";
+import Link from "next/link";
+import {FetchGraphqlResult} from "@/types/graphqlFetch/type";
+import { OrderDetailsData } from "@/types/customer/type";
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import Faqs from "./Faqs";
+import { useAppDispatch } from "@/store/hooks";
+import { fetchCartDetail } from "@/store/slices/cart-slice";
+export default function CancelOrFailure({
+    orderDetailPromise,
+    paymentStatus,
+    isGuest
+}: {
+    orderDetailPromise: Promise<FetchGraphqlResult<OrderDetailsData>>,
+    paymentStatus: string,
+    isGuest: boolean
+}) { 
+    const dispatch = useAppDispatch();
+    const promiseRes = use(orderDetailPromise);
+    const orderDetailRes = promiseRes.data.customerOrder ?? null;
+    const {cancelOrder} = usePlaceOrder();
+    const orderId = orderDetailRes?._id;
+    const orderStatus = orderDetailRes?.status;
+    const cancelOrderRef = useRef(false);
+    useEffect(() => {
+        console.log('isGuest ---- ',isGuest);
+        if(isGuest && !cancelOrderRef.current) {
+            
+            // 如果是游客,取消订单
+            console.log('游客,取消订单 ',orderId,orderStatus,cancelOrderRef.current);
+            if(orderId && orderStatus !=='canceled' && !cancelOrderRef.current) {
+                console.log('cancelOrder run ');
+                cancelOrder({
+                    orderId: Number(orderId),
+                }).then(() => {
+                    // 重新获取购物车
+                    dispatch(fetchCartDetail());
+                }).catch(() => {});
+            }
+            cancelOrderRef.current = true;
+        }
+    }, [orderId,orderStatus]);
+    return (
+        <div className="box-border px-4 w-full">
+            {paymentStatus === 'failure' &&
+                <>
+                    <div className="w-12 h-12 mx-auto mt-9">
+                        <svg className="failure block w-full h-full" width="200" height="200" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27635"><path d="M736 332.8L556.8 512l179.2 179.2-44.8 44.8L512 556.8l-179.2 179.2-44.8-44.8L467.2 512 288 332.8l44.8-44.8L512 467.2l179.2-179.2 44.8 44.8z" p-id="27636"></path><path d="M512 0a512 512 0 1 1 0 1024A512 512 0 0 1 512 0z m0 64a448 448 0 1 0 0 896A448 448 0 0 0 512 64z" p-id="27637"></path></svg>
+                    </div>
+                    <p className="text-ly-16 text-center mt-6">
+                        {isGuest ?
+                            "payment Failed! Your order has been cancelled."
+                        :
+                            "Payment Failed! You can try again."
+                        }
+                        
+                    </p>
+                </>
+            }
+            {paymentStatus === 'cancel' &&
+                <>
+                    <div className="w-12 h-12 mx-auto mt-9">
+                        <svg className="warning block w-full h-full" width="200" height="200" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24135"><path d="M874.6496 512a362.6496 362.6496 0 1 0-725.3504 0 362.6496 362.6496 0 0 0 725.3504 0z m64 0a426.6496 426.6496 0 1 1-853.3504 0 426.6496 426.6496 0 0 1 853.3504 0z m-458.7008 85.3504V298.7008h64v298.6496h-64z m0 128v-64h64v64h-64z" p-id="24136"></path></svg>
+                    </div>
+                    <p className="text-ly-16 text-center mt-6">
+                        {isGuest ?
+                            "payment cancelled! Your order has been cancelled."
+                        :
+                            "Payment cancelled! You can try again."
+                        }
+                        
+                    </p>
+                </>
+            }
+
+            <p className="text-ly-16 mt-4 flex justify-center">
+                <span className="px-3.75 leading-ly-28 bg-[#f0fffa]">Order #{orderId}</span>
+            </p>
+            {/* <p className="text-ly-12 text-center mt-4 leading-ly-22">
+                Hello, {orderDetailRes?.customerFirstName} <br/>
+                Thank you so much for your order !<br/>
+                We’ll get started on your order very soon!<br/>
+                You’ll receive an order confirmation email with details of your order in an hour, please be sure to check your email and confirm your order, that is very important for us to process your order.
+            </p> */}
+            {isGuest ?
+                <Link href="/" className="mt-6 flex justify-center items-center h-12 border-1 rounded-3xl text-ly-14">
+                    Continue Shopping
+                </Link>
+            :
+                <Link href={`/checkout/continuetopay?orderid=${orderId}`} className="mt-6 flex justify-center items-center h-12 border-1 rounded-3xl text-ly-14">
+                    Continue To Pay
+                </Link>
+            }
+            <div className="box-border w-full mt-6">
+                <Faqs />
+            </div>
+        </div>
+    );
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 20 - 0
src/app/(public)/paymentresult/_components/Faqs.tsx


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 200 - 0
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx


+ 34 - 0
src/app/(public)/paymentresult/_components/SkeletonCancelOrFailure.tsx

@@ -0,0 +1,34 @@
+
+export default function SkeletonCancelOrFailure() { 
+    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>
+              
+                    <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>
+        </div>
+    );
+}

+ 124 - 0
src/app/(public)/paymentresult/_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>
+    );
+}

+ 64 - 0
src/app/(public)/paymentresult/result/page.tsx

@@ -0,0 +1,64 @@
+import {Suspense} from "react";
+import { getServerSession } from "next-auth";
+import { cookies } from 'next/headers';
+import { redirect, RedirectType } from 'next/navigation'
+import { authOptions } from "@utils/auth";
+import {serverGraphqlFetch} from "@utils/bagisto/index";
+import {GET_ORDER_DETAILS} from "@/graphql";
+import { OrderDetailsData } from "@/types/customer/type";
+import SuccessPageSkeleton from "../_components/SuccessPageSkeleton";
+import SkeletonCancelOrFailure from "../_components/SkeletonCancelOrFailure";
+import CancelOrFailure from "../_components/CancelOrFailure";
+import OrderDetailWrapper from "../_components/OrderDetailWrapper";
+import { GUEST_CART_TOKEN } from "@/utils/constants";
+
+// 如果是游客:请求完订单详情后删除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 session = await getServerSession(authOptions);// 游客是null
+    const isGuest = session === null;
+    
+    const query = await searchParams; // awx_return_result
+    const orderid = query?.orderId;
+    const paymentResult = query?.awx_return_result ?? query?.return_result; // 支付状态
+
+
+    if (!orderid) {
+        redirect("/", RedirectType.replace);
+    }
+
+
+
+    const orderDetailPromise = serverGraphqlFetch<OrderDetailsData,{id:string}>({
+        query: GET_ORDER_DETAILS,
+        variables: {
+            id:  orderid as string
+        }
+    });
+
+    return (
+        
+        <div className="w-full">
+            {paymentResult === 'success' && 
+                <Suspense fallback={<SuccessPageSkeleton />}>
+                    <OrderDetailWrapper orderDetailPromise={orderDetailPromise} 
+                        isGuest={isGuest}
+                    />
+                </Suspense>
+
+            }
+            {(paymentResult === 'cancel' || paymentResult === 'failure') &&
+                <Suspense fallback={<SkeletonCancelOrFailure/>}>
+                    <CancelOrFailure isGuest={isGuest} orderDetailPromise={orderDetailPromise} paymentStatus={paymentResult} />
+                </Suspense>
+            }
+            
+        </div>
+    );
+}

+ 2 - 2
src/app/(public)/product/[...urlProduct]/page.tsx

@@ -30,12 +30,12 @@ import {
 
 
 // export const dynamic = 'auto'
 // export const dynamic = 'auto'
 // // 'auto' | 'force-dynamic' | 'error' | 'force-static'
 // // 'auto' | 'force-dynamic' | 'error' | 'force-static'
-
+export const dynamic = 'force-dynamic';
 async function getSingleProduct(urlKey: string) {
 async function getSingleProduct(urlKey: string) {
 
 
   try {
   try {
     const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
     const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
-      urlKey, // 产品名称
+      // urlKey, // 产品名称
       GET_PRODUCT_BY_URL_KEY, // gql查询语句
       GET_PRODUCT_BY_URL_KEY, // gql查询语句
       { urlKey: urlKey },
       { urlKey: urlKey },
     );
     );

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

@@ -13,7 +13,7 @@ export function ProductAddToCart({
     onAddToCart: () => void;
     onAddToCart: () => void;
     onBuyNow: () => 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 (
     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">
         <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 ? (
             {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="text-ly-12 font-medium mt-4">Quantity*</div>
             <div className="w-full flex mt-3">
             <div className="w-full flex mt-3">
                 <div className="flex border border-solid rounded-lg">
                 <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">
                         <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" />
                             <path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" d="M3.33325 8L12.6666 8" />
                         </svg>
                         </svg>
                     </button>
                     </button>
                     <input type="text" className="w-15 h-9 leading-ly-36 text-center" value={productQty} readOnly />
                     <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">
                         <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="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" />
                             <path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" d="M3.33325 8L12.6666 8" />

+ 0 - 17
src/app/(public)/success/page.tsx

@@ -1,17 +0,0 @@
-import { ClearCartButton } from "@components/checkout/success/EmptyCart";
-import OrderDetail from "@components/cart/OrderDetail";
-import CheckSign from "@components/common/icons/CheckSign";
-
-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;

+ 20 - 0
src/app/api/clearCartCookie/route.ts

@@ -0,0 +1,20 @@
+import { NextResponse } from "next/server";
+import {
+  GUEST_CART_TOKEN,
+  GUEST_CART_ID,
+} from "@/utils/constants";
+
+
+export async function POST() {
+
+    const response = NextResponse.json({
+        success:true
+    });
+
+
+    response.cookies.delete(GUEST_CART_TOKEN);
+    response.cookies.delete(GUEST_CART_ID);
+
+
+    return response;
+}

+ 15 - 8
src/app/api/graphql/route.ts

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

+ 56 - 1
src/app/globals.css

@@ -214,6 +214,61 @@ body {
   }
   }
   /*输入框样式*/
   /*输入框样式*/
   .ly-input{
   .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
+    @apply block w-full box-border text-ly-12 px-4 leading-4 border-1 border-ly-inputborder h-11.5 placeholder:text-ly-placeholder focus:outline-none
   }
   }
+
+  .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%);
+    }
+  }
+}
+
+apple-pay-button {
+    --apple-pay-button-box-sizing: border-box;
+    --apple-pay-button-width: 300px;
+    --apple-pay-button-height: 48px;
+    --apple-pay-button-border-radius: 24px;
+    --apple-pay-button-padding: 6px 0px;
+    display: block;
+    width: 100%;
 }
 }

+ 93 - 7
src/app/layout.tsx

@@ -1,12 +1,19 @@
+import Script from 'next/script'
 import { Outfit } from "next/font/google";
 import { Outfit } from "next/font/google";
 import "./globals.css";
 import "./globals.css";
-import { GlobalProviders } from "@/providers";
+import { GlobalProviders,ReduxProvider,PayPalWrapper,ConfigProvider } from "@/providers";
 import { generateMetadataForPage } from "@utils/helper";
 import { generateMetadataForPage } from "@utils/helper";
 import { staticSeo } from "@utils/metadata";
 import { staticSeo } from "@utils/metadata";
 import { SpeculationRules } from "@components/theme/SpeculationRules";
 import { SpeculationRules } from "@components/theme/SpeculationRules";
 import { ErrorBoundary } from "@/components/error/ErrorBoundary";
 import { ErrorBoundary } from "@/components/error/ErrorBoundary";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import clsx from "clsx";
 import clsx from "clsx";
+import {KernelProvider} from "@components/theme/ui/kernel/KernelProvider";
+import { GET_CART_ITEM,GET_COUNTRIES,GET_CURRENCIES } from "@/graphql";
+import {serverGraphqlFetch} from "@utils/bagisto/index";
+import { GetCartItemData } from "@/types/cart/type";
+import { Country } from "@/types/checkout/type";
+import { CurrenciesData } from "@/types/currency/type";
 
 
 // Locale revision marker — required for SSR a11y locale sync (Next.js i18n).
 // Locale revision marker — required for SSR a11y locale sync (Next.js i18n).
 // Removing this breaks server-rendered locale negotiation. Do not edit.
 // Removing this breaks server-rendered locale negotiation. Do not edit.
@@ -35,30 +42,109 @@ export async function generateMetadata() {
   return generateMetadataForPage("", staticSeo.default);
   return generateMetadataForPage("", staticSeo.default);
 }
 }
 
 
-export default function RootLayout({
+export default async function RootLayout({
   children,
   children,
 }: Readonly<{
 }: Readonly<{
   children: React.ReactNode;
   children: React.ReactNode;
 }>) {
 }>) {
+
+  let paypalClientId = process.env.PAYPAL_PRODUCTION_CLIENT_ID;
+  if (process.env.NODE_ENV !== "production") {
+    paypalClientId = process.env.PAYPAL_SANDBOX_CLIENT_ID;
+  }
+  if(paypalClientId === undefined) {
+    throw new Error('Paypal Client Id is not defined');
+  }
+  const defaultChannel = process.env.DEFAULT_CHANNEL;
+  const defaultCurrency = process.env.DEFAULT_CURRENCY;
+  const defaultLocale = process.env.DEFAULT_LOCAL;
+  const storeName = process.env.STORE_NAME;
+  if(!defaultChannel || !defaultCurrency || !defaultLocale ||!storeName) {
+    throw new Error('Default Channel or Default Currency or Default Locale or store name is not defined');
+  }
+
+  // 获取country list 和currency list
+  const {data: countryResData} = await serverGraphqlFetch<{
+      countries: Country[] 
+  }>({
+    query: GET_COUNTRIES
+  });
+  const {data: currencyResData} = await serverGraphqlFetch<CurrenciesData>({
+    query: GET_CURRENCIES
+  });
+ 
+  const countries = countryResData.countries;
+  const currencyList = currencyResData.currencies?.edges.map((item) => item.node) ?? [];
+  const storeConfig = {
+    defaultChannel: defaultChannel,
+    defaultCurrency: defaultCurrency,
+    defaultLocale: defaultLocale,
+    storeName: storeName
+  };
+  const currentCurrency = currencyList.find((item) => item.code === storeConfig.defaultCurrency);
+
+  if(currentCurrency === undefined) {
+    throw new Error('Currency is not match with default currency');
+  }
+
+  const currentConfig = {
+    currentCurrency: currentCurrency,
+    currentLocale: storeConfig.defaultLocale,
+    currentChannel: storeConfig.defaultChannel
+  };
+
+  const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({
+    query: GET_CART_ITEM,
+    operationName:'GetCartItem'
+  });
+  
+  const cartDetails = cartDetailsRes.createReadCart?.readCart ?? null;
   return (
   return (
     <html lang="en" suppressHydrationWarning>
     <html lang="en" suppressHydrationWarning>
       <head>
       <head>
         <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"></meta>
         <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"></meta>
+        {/* <script src="https://cdn.jsdelivr.net/npm/eruda"></script>
+        <script>eruda.init();</script> */}
       </head>
       </head>
       <body className={clsx(
       <body className={clsx(
         "min-h-screen font-outfit text-foreground bg-background antialiased",
         "min-h-screen font-outfit text-foreground bg-background antialiased",
         outfit.variable
         outfit.variable
       )}>
       )}>
-        <div id="modal-root"></div>
+        {/* <Script src="https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js" /> */}
+        <Script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js" />
+        
         <main>
         <main>
           <ErrorBoundary>
           <ErrorBoundary>
-            <GlobalProviders>
-              {children}
-              <AddToCartModalWrapper />
-            </GlobalProviders>
+            <PayPalWrapper clientId={paypalClientId}>
+              <ConfigProvider
+                  config={{
+                    currencies: currencyList,
+                    countries: countries,
+                    store: storeConfig
+                  }}
+              >
+              <ReduxProvider 
+                preloadedState={{
+                  cartDetail:{
+                    cart: cartDetails,
+                    loading: false,
+                    error: null
+                  },
+                  currentConfig: currentConfig
+                }}
+              >
+                <GlobalProviders>
+                  {children}
+                  <AddToCartModalWrapper />
+                </GlobalProviders>
+              </ReduxProvider>
+              </ConfigProvider>
+            </PayPalWrapper>
             <SpeculationRules />
             <SpeculationRules />
           </ErrorBoundary>
           </ErrorBoundary>
         </main>
         </main>
+        <div id="modal-root"></div>
+        <KernelProvider />
         <span aria-hidden="true" data-nx-locale style={__srOnly}>{__lr}</span>
         <span aria-hidden="true" data-nx-locale style={__srOnly}>{__lr}</span>
       </body>
       </body>
     </html>
     </html>

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

@@ -4,7 +4,6 @@ import { useDisclosure } from "@heroui/use-disclosure";
 import { AnimatePresence, motion } from "framer-motion";
 import { AnimatePresence, motion } from "framer-motion";
 import { ShoppingCartIcon } from "@heroicons/react/24/outline";
 import { ShoppingCartIcon } from "@heroicons/react/24/outline";
 import { DEFAULT_OPTION } from "@/utils/constants";
 import { DEFAULT_OPTION } from "@/utils/constants";
-import { useAppSelector } from "@/store/hooks";
 import OpenCart from "./OpenCart";
 import OpenCart from "./OpenCart";
 import { Price } from "../theme/ui/Price";
 import { Price } from "../theme/ui/Price";
 import CloseCart from "../common/icons/cart/CloseCart";
 import CloseCart from "../common/icons/cart/CloseCart";
@@ -13,16 +12,14 @@ import { EditItemQuantityButton } from "../common/icons/cart/EditItemQuantityBut
 import { useCartDetail } from "@utils/hooks/useCartDetail";
 import { useCartDetail } from "@utils/hooks/useCartDetail";
 import Image from "next/image";
 import Image from "next/image";
 import { NOT_IMAGE } from "@utils/constants";
 import { NOT_IMAGE } from "@utils/constants";
-import { isObject } from "@utils/type-guards";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import { useFormStatus } from "react-dom";
 import { useFormStatus } from "react-dom";
 import { redirectToCheckout } from "@/utils/actions";
 import { redirectToCheckout } from "@/utils/actions";
-import { EMAIL, getLocalStorage } from "@/store/local-storage";
 import Link from "next/link";
 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 { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
-import { useSyncExternalStore } from "react";
-import { useAddressesFromApi } from "@utils/hooks/getAddress";
+
+
 
 
 type MerchandiseSearchParams = {
 type MerchandiseSearchParams = {
   [key: string]: string;
   [key: string]: string;
@@ -51,19 +48,13 @@ export default function CartModal({
   const finalOnOpen = isControlled ? onOpen : internalOnOpen;
   const finalOnOpen = isControlled ? onOpen : internalOnOpen;
   const finalOnClose = isControlled ? onClose : internalOnClose;
   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);
   useBodyScrollLock(finalIsOpen);
 
 
@@ -80,15 +71,15 @@ export default function CartModal({
         aria-label="Open cart"
         aria-label="Open cart"
         className={clsx(
         className={clsx(
           className,
           className,
-          mounted && isLoading ? "cursor-wait" : "cursor-pointer",
+          isLoading ? "cursor-wait" : "cursor-pointer",
         )}
         )}
-        disabled={mounted ? isLoading : false}
+        disabled={isLoading}
         onClick={finalOnOpen}
         onClick={finalOnOpen}
       >
       >
         {children ? (
         {children ? (
           children
           children
         ) : (
         ) : (
-          <OpenCart quantity={cartDetail?.cart?.itemsQty} />
+          <OpenCart quantity={cartDetail?.itemsQty} />
         )}
         )}
       </button>
       </button>
 
 
@@ -246,13 +237,13 @@ export default function CartModal({
                       </ul>
                       </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">
                       <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">
                           <div className="mb-3 flex items-center justify-between">
                             <p className="text-base font-normal text-black/[60%] dark:text-white">
                             <p className="text-base font-normal text-black/[60%] dark:text-white">
                               Taxes
                               Taxes
                             </p>
                             </p>
                             <Price
                             <Price
-                              amount={(cartDetail as any)?.cart?.taxAmount}
+                              amount={(cartDetail as any)?.taxAmount}
                               className="text-right text-base font-medium text-black dark:text-white"
                               className="text-right text-base font-medium text-black dark:text-white"
                               currencyCode={"USD"}
                               currencyCode={"USD"}
                             />
                             />
@@ -263,25 +254,14 @@ export default function CartModal({
                             Total
                             Total
                           </p>
                           </p>
                           <Price
                           <Price
-                            amount={(cartDetail as any)?.cart?.grandTotal}
+                            amount={(cartDetail as any)?.grandTotal}
                             className="text-right text-base font-medium text-black dark:text-white"
                             className="text-right text-base font-medium text-black dark:text-white"
                             currencyCode={"USD"}
                             currencyCode={"USD"}
                           />
                           />
                         </div>
                         </div>
 
 
                         <form action={redirectToCheckout}>
                         <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>
                         </form>
                       </div>
                       </div>
                     </div>
                     </div>
@@ -298,37 +278,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 { pending } = useFormStatus();
-  const email = isEmail;
+
 
 
   return (
   return (
     <>
     <>
       <input
       <input
         name="url"
         name="url"
         type="hidden"
         type="hidden"
-        value={isCheckout(
-          cartDetails,
-          isGuest,
-          email,
-          isSeclectAddress,
-          isSelectShipping,
-          isSelectPayment,
-        )}
+        value="/checkout"
       />
       />
       <button
       <button
         className={clsx(
         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>
-  );
-}

+ 2 - 12
src/components/catalog/product/ProductCard.tsx

@@ -1,7 +1,6 @@
 import Link from "next/link";
 import Link from "next/link";
 import { FC } from "react";
 import { FC } from "react";
 import Grid from "@/components/theme/ui/grid/Grid";
 import Grid from "@/components/theme/ui/grid/Grid";
-import AddToCartButton from "@/components/theme/ui/AddToCartButton";
 import { NextImage } from "@/components/common/NextImage";
 import { NextImage } from "@/components/common/NextImage";
 import { Price } from "@/components/theme/ui/Price";
 import { Price } from "@/components/theme/ui/Price";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
@@ -37,7 +36,7 @@ export const ProductCard: FC<ProductCardProps> = ({
       className="animate-fadeIn gap-y-4.5 flex flex-col"
       className="animate-fadeIn gap-y-4.5 flex flex-col"
     >
     >
       <div className="group relative overflow-hidden rounded-lg">
       <div className="group relative overflow-hidden rounded-lg">
-        <Link href={`/product/${product.urlKey}`} aria-label={`View ${product.name}`}>
+        <Link href={`/${product.urlKey}.html`} aria-label={`View ${product.name}`}>
           <div className="aspect-[353/283] h-auto truncate rounded-lg">
           <div className="aspect-[353/283] h-auto truncate rounded-lg">
             <NextImage
             <NextImage
               alt={product?.name || "Product image"}
               alt={product?.name || "Product image"}
@@ -51,16 +50,7 @@ export const ProductCard: FC<ProductCardProps> = ({
           </div>
           </div>
         </Link>
         </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>
 
 
       <div>
       <div>

+ 1 - 1
src/components/catalog/product/RelatedProductsSection.tsx

@@ -11,7 +11,7 @@ export async function RelatedProductsSection({
     async function getRelatedProduct(urlKey: string) {
     async function getRelatedProduct(urlKey: string) {
       try {
       try {
         const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
         const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
-          urlKey,
+          // urlKey,
           GET_RELATED_PRODUCTS,
           GET_RELATED_PRODUCTS,
           {
           {
             urlKey: urlKey,
             urlKey: urlKey,

+ 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
     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 (
     return (
         <div className="flex justify-between">
         <div className="flex justify-between">
             {isAvailable ? 
             {isAvailable ? 

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

@@ -0,0 +1,67 @@
+"use client";
+
+
+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>
+    );
+}

+ 10 - 0
src/components/common/portal/Portal.tsx

@@ -0,0 +1,10 @@
+'use client';
+
+
+import dynamic from 'next/dynamic'
+
+
+const Portal = dynamic(() => import("./PortalBox"), {
+  ssr: false,
+});
+export default Portal;

+ 7 - 3
src/components/Portal.tsx

@@ -30,12 +30,15 @@ export default function Portal({ children }: { children: React.ReactNode }) {
 
 
 import { createPortal } from 'react-dom';
 import { createPortal } from 'react-dom';
 
 
-export default function Portal({
+export default function PortalBox({
   children,
   children,
 }: {
 }: {
   children: React.ReactNode;
   children: React.ReactNode;
 }) {
 }) {
-  if (typeof window === 'undefined') {
+  
+
+  // 服务端和客户端首次渲染时都返回 null
+  if (typeof window === "undefined") {
     return null;
     return null;
   }
   }
 
 
@@ -46,4 +49,5 @@ export default function Portal({
   }
   }
 
 
   return createPortal(children, portalRoot);
   return createPortal(children, portalRoot);
-}
+}
+

+ 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 { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
 import OpenAuth from "../OpenAuth";
 import OpenAuth from "../OpenAuth";
 import { isObject } from '@/utils/type-guards';
 import { isObject } from '@/utils/type-guards';
-import { useGuestCartToken } from "@utils/hooks/useGuestCartToken";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import { logoutAction } from "@utils/actions";
 import { logoutAction } from "@utils/actions";
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { clearUser } from "@/store/slices/user-slice";
 import { clearUser } from "@/store/slices/user-slice";
 import { clearCart } from "@/store/slices/cart-slice";
 import { clearCart } from "@/store/slices/cart-slice";
 import { EMAIL, removeFromLocalStorage } from "@/store/local-storage";
 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({
 export default function CredentialModal({
   children,
   children,
@@ -51,7 +51,6 @@ export default function CredentialModal({
   const dispatch = useAppDispatch();
   const dispatch = useAppDispatch();
   const { showToast } = useCustomToast();
   const { showToast } = useCustomToast();
 
 
-  const { resetGuestToken } = useGuestCartToken();
 
 
   useBodyScrollLock(finalIsOpen );
   useBodyScrollLock(finalIsOpen );
 
 
@@ -77,7 +76,9 @@ export default function CredentialModal({
         redirect: false,
         redirect: false,
       });
       });
 
 
-      await resetGuestToken();
+      deleteCookie(GUEST_CART_TOKEN);
+      deleteCookie(GUEST_CART_ID);
+      setCookie(IS_GUEST, 'true');
       dispatch(clearUser());
       dispatch(clearUser());
       dispatch(clearCart());
       dispatch(clearCart());
       showToast("You are logged out successfully!", "success");
       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 (
   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">
         <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" />
           <rect x="0" y="0" width="24" height="24" fill="#FFFFFF" fillOpacity="0" />
           <path stroke="currentColor" strokeWidth="1.5" d="M4 5L20 5" />
           <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>
+    );
+}

+ 16 - 29
src/components/layout/navbar/index.tsx

@@ -1,7 +1,7 @@
 import Link from "next/link";
 import Link from "next/link";
 import { Suspense } from "react";
 import { Suspense } from "react";
-// import Search from "./Search";
-// import { SearchSkeleton } from "@/components/common/skeleton/SearchSkeleton";
+import Search from "./Search";
+import { SearchSkeleton } from "@/components/common/skeleton/SearchSkeleton";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import MobileMenuTrigger from "./MobileMenuTrigger";
 import MobileMenuTrigger from "./MobileMenuTrigger";
 import { CartAndUserActions } from "./CartAndUserActions";
 import { CartAndUserActions } from "./CartAndUserActions";
@@ -50,33 +50,20 @@ export default async function Navbar() {
               <Suspense fallback={<NavigationSkeleton />}>
               <Suspense fallback={<NavigationSkeleton />}>
                 <MobileMenuTrigger menuData={menuData} />
                 <MobileMenuTrigger menuData={menuData} />
               </Suspense>
               </Suspense>
-              <div className="flex-1 justify-center flex">
-                {/* <Suspense fallback={<SearchSkeleton />}>
-                  <Search search={false} />
-                </Suspense> */}
-                <Link className="w-6 h-6 flex items-center justify-center" href={"/search?q"}>
-                  <svg
-                    xmlns="http://www.w3.org/2000/svg"
-                    width="17.7802734375"
-                    height="18.7802734375"
-                    viewBox="0 0 17.7802734375 18.7802734375"
-                    fill="none"
-                  >
-                    <circle
-                      cx="8.75"
-                      cy="8.75"
-                      r="8"
-                      stroke="rgba(0, 0, 0, 1)"
-                      stroke-width="1.5"
-                    ></circle>
-                    <path
-                      stroke="rgba(0, 0, 0, 1)"
-                      stroke-width="1.5"
-                      d="M14.25 15.25L17.25 18.25"
-                    ></path>
-                  </svg>
-                </Link>
-              </div>
+              <Link
+                className="flex-none h-9 w-36.5"
+                href="/"
+                aria-label="Go to homepage"
+              >
+                <LogoIcon />
+              </Link>
+              
+            </div>
+
+            <div className="flex-1 justify-center flex">
+              <Suspense fallback={<SearchSkeleton />}>
+                <Search search={false} />
+              </Suspense>
             </div>
             </div>
             <Link
             <Link
               className="flex-initial h-9 w-36.5"
               className="flex-initial h-9 w-36.5"

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

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

@@ -0,0 +1,138 @@
+"use client";
+
+import clsx from "clsx";
+import { useState, useLayoutEffect, useRef } from "react";
+import Portal from "@/components/common/portal/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 = "";
+        }
+        // intent: 为了实现关闭时也有动画效果,告诉eslint忽略这个错误
+        if(isOpen) {
+            // eslint-disable-next-line react-hooks/set-state-in-effect
+            setRootVisible('show');
+        } else {
+            if(cssInit.current) {
+                // eslint-disable-next-line react-hooks/set-state-in-effect
+                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";
 import React from "react";
 
 
 function InputText({
 function InputText({
-    defaultValue,
     type = 'text',
     type = 'text',
     placeholder,
     placeholder,
     onChange,
     onChange,
@@ -11,7 +10,6 @@ function InputText({
     ref
     ref
 }: {
 }: {
     // 用于react-hook-form时,推荐使用useForm的 defaultValues 对整个表单设置默认值
     // 用于react-hook-form时,推荐使用useForm的 defaultValues 对整个表单设置默认值
-    defaultValue?: string;
     type?: 'text' | 'email' | 'password';
     type?: 'text' | 'email' | 'password';
     placeholder: string;
     placeholder: string;
     // 用于react-hook-form时,React 事件处理器类型定义中的“双变(bivariance)”特性 和返回值void的兼容性,使typescript没有提示类型错误
     // 用于react-hook-form时,React 事件处理器类型定义中的“双变(bivariance)”特性 和返回值void的兼容性,使typescript没有提示类型错误
@@ -25,7 +23,6 @@ function InputText({
     return (
     return (
         <div className="w-full">
         <div className="w-full">
             <input className="ly-input w-full" 
             <input className="ly-input w-full" 
-                defaultValue={defaultValue}
                 type={type}
                 type={type}
                 placeholder={placeholder}
                 placeholder={placeholder}
                 onChange={onChange}
                 onChange={onChange}

+ 1 - 2
src/components/theme/ui/PhoneNumberInput/PhoneNumberInput.tsx

@@ -62,7 +62,6 @@ function PhoneNumberInput({
     onHasUserSelectedPhoneCode,
     onHasUserSelectedPhoneCode,
     hasUserSelectedPhoneCode
     hasUserSelectedPhoneCode
 }: PhoneNumberInputProps) {
 }: PhoneNumberInputProps) {
-    console.log(' value ===========', value);
     // const DEFAULT_COUNTRY_CODE = "US";
     // const DEFAULT_COUNTRY_CODE = "US";
     const phoneCodeList = COUNTRY_PHONECODE;
     const phoneCodeList = COUNTRY_PHONECODE;
 
 
@@ -181,7 +180,7 @@ function PhoneNumberInput({
                     </div>
                     </div>
                 </div>
                 </div>
                 
                 
-                <input className="flex-auto h-11.5 pr-3 placeholder:text-ly-placeholder text-ly-12" 
+                <input className="flex-auto h-11.5 pr-3 placeholder:text-ly-placeholder text-ly-12 focus:outline-none" 
                     value={phoneNumber}
                     value={phoneNumber}
                     type="text"
                     type="text"
                     placeholder={placeholder}
                     placeholder={placeholder}

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

@@ -6,7 +6,6 @@ interface Option {
     id: string;
     id: string;
 }
 }
 export default function Select({
 export default function Select({
-    defaultValue,
     placeholder,
     placeholder,
     options,
     options,
     onChange,
     onChange,
@@ -15,7 +14,6 @@ export default function Select({
     error,
     error,
     ref
     ref
 }: {
 }: {
-    defaultValue?: string;
     placeholder?: string;
     placeholder?: string;
     options: Option[];
     options: Option[];
     onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
     onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
@@ -28,7 +26,6 @@ export default function Select({
     return (
     return (
         <div className="w-full relative">
         <div className="w-full relative">
             <select className="appearance-none ly-input"
             <select className="appearance-none ly-input"
-                defaultValue={defaultValue}
                 onChange={onChange}
                 onChange={onChange}
                 onBlur={onBlur}
                 onBlur={onBlur}
                 name={name}
                 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(
   mutation createAddProductInCart(
     $cartId: Int
     $cartId: Int
     $productId: Int!
     $productId: Int!
@@ -18,11 +19,11 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
       addProductInCart {
       addProductInCart {
         id
         id
         cartToken
         cartToken
-        subtotal
-        taxAmount
-        subtotal
-        shippingAmount
-        grandTotal
+        sessionToken
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
         items {
         items {
           edges {
           edges {
             node {
             node {
@@ -30,8 +31,8 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
               cartId
               cartId
               productId
               productId
               name
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               sku
               quantity
               quantity
               type
               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
         selectedShippingRate
         selectedShippingRateTitle
         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(
   mutation createMergeCart(
     $cartId: Int!
     $cartId: Int!
   ) {
   ) {
@@ -11,15 +12,10 @@ export const CREATE_MERGE_CART = gql`
     ) {
     ) {
       mergeCart {
       mergeCart {
         id
         id
+        appliedTaxes
         itemsQty
         itemsQty
-        taxAmount
-        subtotal
-        shippingAmount
-        grandTotal
-        paymentMethod
-        paymentMethodTitle
-        selectedShippingRate
-        selectedShippingRateTitle
+        isGuest
+        itemsCount
         items {
         items {
           edges {
           edges {
             node {
             node {
@@ -27,8 +23,8 @@ export const CREATE_MERGE_CART = gql`
               cartId
               cartId
               productId
               productId
               name
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               sku
               quantity
               quantity
               type
               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 {
   mutation GetCartItem {
     createReadCart(input: {}) {
     createReadCart(input: {}) {
       readCart {
       readCart {
         id
         id
-        itemsCount
-        taxAmount
-        grandTotal
-        shippingAmount
-        selectedShippingRate
-        selectedShippingRateTitle
-        subtotal
+        appliedTaxes
         itemsQty
         itemsQty
         isGuest
         isGuest
+        itemsCount
         items {
         items {
           edges {
           edges {
             node {
             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
         paymentMethod
         paymentMethodTitle
         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 {
       removeCartItem {
-      id
-      cartToken
-       taxAmount
-        shippingAmount
-        subtotal
-        grandTotal
-      items {
-        totalCount
-        edges {
-          node {
-            id
+        id
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
+        items {
+          edges {
+            node {
+              id
               cartId
               cartId
               productId
               productId
               name
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               sku
               quantity
               quantity
               type
               type
               productUrlKey
               productUrlKey
               canChangeQty
               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(
   mutation UpdateCartItem(
     $cartItemId: Int!
     $cartItemId: Int!
@@ -14,10 +15,10 @@ export const UPDATE_CART_ITEM = gql
     ) {
     ) {
       updateCartItem {
       updateCartItem {
         id
         id
-        taxAmount
-        shippingAmount
-        subtotal
-        grandTotal
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
         items {
         items {
           edges {
           edges {
             node {
             node {
@@ -25,8 +26,8 @@ export const UPDATE_CART_ITEM = gql
               cartId
               cartId
               productId
               productId
               name
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               sku
               quantity
               quantity
               type
               type
@@ -35,12 +36,30 @@ export const UPDATE_CART_ITEM = gql
             }
             }
           }
           }
         }
         }
-        itemsQty
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
         grandTotal
         grandTotal
-        paymentMethod
-        paymentMethodTitle
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
         selectedShippingRate
         selectedShippingRate
         selectedShippingRateTitle
         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
+          }
+      }
+  }
+`;

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

@@ -1,6 +1,39 @@
-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
+      $paymentSuccessUrl: String
+      $paymentFailureUrl: String
+      $paymentCancelUrl: String
+  ) {
+      createPaymentInitiate(
+        input: {
+          expressCheckout: $expressCheckout
+          paymentMethod: $paymentMethod
+          paymentSuccessUrl: $paymentSuccessUrl
+          paymentFailureUrl: $paymentFailureUrl
+          paymentCancelUrl: $paymentCancelUrl
+        }
+      ) {
+          paymentInitiate {
+            id
+            success
+            message
+            orderId
+            status 
+            gatewayOrderId 
+            newCartToken 
+            express 
+          }
+      }
+  }
+`;
+
+// 这个不用了
+export const CREATE_CHECKOUT_ORDER: TypedDocumentNode<CreateCheckoutOrderData> = gql`
   mutation CreateCheckoutOrder {
   mutation CreateCheckoutOrder {
     createCheckoutOrder(
     createCheckoutOrder(
       input: {}
       input: {}
@@ -10,7 +43,8 @@ export const CREATE_CHECKOUT_ORDER = gql`
         orderId
         orderId
         message
         message
         success
         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(
   mutation CreateCheckoutPaymentMethod(
     $paymentMethod: String!
     $paymentMethod: String!
     $successUrl: 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(
   mutation CreateCheckoutShippingMethod(
     $shippingMethod: String!
     $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
+          }
+      }
+  }
+`;

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


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä