Quellcode durchsuchen

apollo client 从v3.14 升级到 v4.1.9

fogwind vor 5 Tagen
Ursprung
Commit
894dd5bfd6
35 geänderte Dateien mit 397 neuen und 219 gelöschten Zeilen
  1. 4 1
      README.md
  2. 3 1
      package.json
  3. 64 65
      pnpm-lock.yaml
  4. 6 6
      src/app/(public)/category/[collection]/page.tsx
  5. 2 2
      src/app/(public)/page.tsx
  6. 1 1
      src/app/(public)/product/[...urlProduct]/page.tsx
  7. 1 1
      src/app/(public)/product/_components/ProductInformation.tsx
  8. 7 8
      src/app/(public)/search/page.tsx
  9. 2 2
      src/components/catalog/product/RelatedProductsSection.tsx
  10. 3 2
      src/components/checkout/stepper/payment/index.tsx
  11. 3 3
      src/components/checkout/stepper/shipping/index.tsx
  12. 1 1
      src/components/home/CategoryCarousel.tsx
  13. 1 1
      src/components/home/ProductCarousel.tsx
  14. 1 1
      src/components/layout/navbar/index.tsx
  15. 0 1
      src/graphql/cart/mutations/AddProductToCart.ts
  16. 2 0
      src/graphql/checkout/mutations/CreateCheckoutOrder.ts
  17. 1 0
      src/graphql/checkout/queries/GetCheckoutPaymentMethods.ts
  18. 1 1
      src/graphql/index.ts
  19. 79 0
      src/lib/ApolloClientBrowser.ts
  20. 47 0
      src/lib/ApolloClientServer.ts
  21. 97 52
      src/lib/graphql-fetch.ts
  22. 4 4
      src/providers/ApolloWrapper.tsx
  23. 1 0
      src/types/cart/type.ts
  24. 5 8
      src/types/checkout/type.ts
  25. 1 1
      src/utils/bagisto/index.ts
  26. 7 34
      src/utils/helper.ts
  27. 1 1
      src/utils/hooks/getProductReviews.ts
  28. 1 1
      src/utils/hooks/getProductSwatchAndReview.ts
  29. 5 4
      src/utils/hooks/useAddToCart.ts
  30. 3 3
      src/utils/hooks/useAddress.ts
  31. 30 4
      src/utils/hooks/useCache.ts
  32. 3 2
      src/utils/hooks/useCartDetail.ts
  33. 6 5
      src/utils/hooks/useCheckout.ts
  34. 3 2
      src/utils/hooks/useMergeCart.ts
  35. 1 1
      src/utils/hooks/useProductReview.ts

+ 4 - 1
README.md

@@ -217,4 +217,7 @@ COMPANY_NAME=Your Company Name
 
 ## 关于dangerouslySetInnerHTML和JSON-LD
 1. [dangerouslySetInnerHTML](https://blog.csdn.net/weixin_43172311/article/details/154724433)
-2. [JSON-LD](https://www.cnblogs.com/swizard/p/19476232)
+2. [JSON-LD](https://www.cnblogs.com/swizard/p/19476232)
+
+
+@todo 购物车详情优化(cart-slice中规定了购物车数据的type,但是接口返回数据明显与规定的数据类型不匹配,还硬往购物车state里存)

+ 3 - 1
package.json

@@ -11,7 +11,8 @@
     "package-version": "npx npm-check-updates"
   },
   "dependencies": {
-    "@apollo/client": "^3.14.0",
+    "@apollo/client": "^4.1.9",
+    "@apollo/client-integration-nextjs": "^0.14.5",
     "@heroicons/react": "^2.2.0",
     "@heroui/accordion": "^2.2.25",
     "@heroui/alert": "^2.2.32",
@@ -43,6 +44,7 @@
     "react-dom": "19.2.5",
     "react-hook-form": "^7.66.1",
     "react-redux": "^9.2.0",
+    "rxjs": "^7.8.2",
     "swiper": "^12.1.3"
   },
   "devDependencies": {

+ 64 - 65
pnpm-lock.yaml

@@ -13,8 +13,11 @@ importers:
   .:
     dependencies:
       '@apollo/client':
-        specifier: ^3.14.0
-        version: 3.14.1(@types/react@19.2.14)(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+        specifier: ^4.1.9
+        version: 4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
+      '@apollo/client-integration-nextjs':
+        specifier: ^0.14.5
+        version: 0.14.5(@apollo/client@4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.13.2)(next@16.2.3(@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)
       '@heroicons/react':
         specifier: ^2.2.0
         version: 2.2.0(react@19.2.5)
@@ -108,6 +111,9 @@ importers:
       react-redux:
         specifier: ^9.2.0
         version: 9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1)
+      rxjs:
+        specifier: ^7.8.2
+        version: 7.8.2
       swiper:
         specifier: ^12.1.3
         version: 12.1.3
@@ -167,13 +173,31 @@ packages:
     resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
     engines: {node: '>=10'}
 
-  '@apollo/client@3.14.1':
-    resolution: {integrity: sha512-SgGX6E23JsZhUdG2anxiyHvEvvN6CUaI4ZfMsndZFeuHPXL3H0IsaiNAhLITSISbeyeYd+CBd9oERXQDdjXWZw==}
+  '@apollo/client-integration-nextjs@0.14.5':
+    resolution: {integrity: sha512-aNh16zlx5rrGtP946jW+O/J7OUyoSC0dxjicFIztiw/S5tQjNqZgqmlgtOXMn7zAR8qV9VOAiKjK5UROaN8iJg==}
     peerDependencies:
-      graphql: ^15.0.0 || ^16.0.0
+      '@apollo/client': ^4.0.0
+      next: ^15.2.3 || ^16.0.0
+      react: ^19
+      rxjs: ^7.3.0
+
+  '@apollo/client-react-streaming@0.14.5':
+    resolution: {integrity: sha512-ru9FP4g5tULITsWBO6oDSHOflnZaD6lm0AtjT9mkZbbrUkWwoYcSrTKdjNrjfrnppqXS1igv+DpcO+SlXdUGXg==}
+    peerDependencies:
+      '@apollo/client': ^4.0.0
+      graphql: ^16 || >=17.0.0-alpha.2
+      react: ^19
+      react-dom: ^19
+      rxjs: ^7.3.0
+
+  '@apollo/client@4.1.9':
+    resolution: {integrity: sha512-qfpkQD51tdU/7iAR6aLb4w9o/L7I475DluWHRb61U/3Q0AH29nNOxOBHjBbWDdf16ncPOoQuxne1sEs2NjqBFw==}
+    peerDependencies:
+      graphql: ^16.0.0
       graphql-ws: ^5.5.5 || ^6.0.3
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc
+      react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc
+      react-dom: ^17.0.0 || ^18.0.0 || >=19.0.0-rc
+      rxjs: ^7.3.0
       subscriptions-transport-ws: ^0.9.0 || ^0.11.0
     peerDependenciesMeta:
       graphql-ws:
@@ -2219,9 +2243,6 @@ packages:
   hi-base32@0.5.1:
     resolution: {integrity: sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==}
 
-  hoist-non-react-statics@3.3.2:
-    resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
-
   ignore@5.3.2:
     resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
     engines: {node: '>= 4'}
@@ -2814,17 +2835,6 @@ packages:
     resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
     engines: {node: '>= 0.4'}
 
-  rehackt@0.1.0:
-    resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==}
-    peerDependencies:
-      '@types/react': 19.2.14
-      react: '*'
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      react:
-        optional: true
-
   reselect@5.1.1:
     resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
 
@@ -2852,6 +2862,9 @@ packages:
   run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
+  rxjs@7.8.2:
+    resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
   safe-array-concat@1.1.3:
     resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
     engines: {node: '>=0.4'}
@@ -2992,10 +3005,6 @@ packages:
     resolution: {integrity: sha512-XcWlVmkHFICI4fuoJKgbp8PscDcS4i7pBH8nwJRBi3dpQvhCySwsWRYm4bOf/BzKVWkHOYaFw7qz9uBSrY3oug==}
     engines: {node: '>= 4.7.0'}
 
-  symbol-observable@4.0.0:
-    resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==}
-    engines: {node: '>=0.10'}
-
   tailwind-merge@3.4.0:
     resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
 
@@ -3030,10 +3039,6 @@ packages:
     peerDependencies:
       typescript: '>=4.8.4'
 
-  ts-invariant@0.10.3:
-    resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==}
-    engines: {node: '>=8'}
-
   ts-node@10.9.2:
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
     hasBin: true
@@ -3183,12 +3188,6 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
-  zen-observable-ts@1.2.5:
-    resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==}
-
-  zen-observable@0.8.15:
-    resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==}
-
   zod-validation-error@4.0.2:
     resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
     engines: {node: '>=18.0.0'}
@@ -3229,7 +3228,31 @@ snapshots:
 
   '@alloc/quick-lru@5.2.0': {}
 
-  '@apollo/client@3.14.1(@types/react@19.2.14)(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
+  '@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.13.2)(next@16.2.3(@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)':
+    dependencies:
+      '@apollo/client': 4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
+      '@apollo/client-react-streaming': 0.14.5(@apollo/client@4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
+      next: 16.2.3(@babel/core@7.29.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+      react: 19.2.5
+      rxjs: 7.8.2
+    transitivePeerDependencies:
+      - '@types/react'
+      - graphql
+      - react-dom
+
+  '@apollo/client-react-streaming@0.14.5(@apollo/client@4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
+    dependencies:
+      '@apollo/client': 4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
+      '@types/react-dom': 19.2.3(@types/react@19.2.14)
+      '@wry/equality': 0.5.7
+      graphql: 16.13.2
+      react: 19.2.5
+      react-dom: 19.2.5(react@19.2.5)
+      rxjs: 7.8.2
+    transitivePeerDependencies:
+      - '@types/react'
+
+  '@apollo/client@4.1.9(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
     dependencies:
       '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2)
       '@wry/caches': 1.0.1
@@ -3237,19 +3260,12 @@ snapshots:
       '@wry/trie': 0.5.0
       graphql: 16.13.2
       graphql-tag: 2.12.6(graphql@16.13.2)
-      hoist-non-react-statics: 3.3.2
       optimism: 0.18.1
-      prop-types: 15.8.1
-      rehackt: 0.1.0(@types/react@19.2.14)(react@19.2.5)
-      symbol-observable: 4.0.0
-      ts-invariant: 0.10.3
+      rxjs: 7.8.2
       tslib: 2.8.1
-      zen-observable-ts: 1.2.5
     optionalDependencies:
       react: 19.2.5
       react-dom: 19.2.5(react@19.2.5)
-    transitivePeerDependencies:
-      - '@types/react'
 
   '@babel/code-frame@7.29.0':
     dependencies:
@@ -5824,10 +5840,6 @@ snapshots:
 
   hi-base32@0.5.1: {}
 
-  hoist-non-react-statics@3.3.2:
-    dependencies:
-      react-is: 16.13.1
-
   ignore@5.3.2: {}
 
   ignore@7.0.5: {}
@@ -6424,11 +6436,6 @@ snapshots:
       gopd: 1.2.0
       set-function-name: 2.0.2
 
-  rehackt@0.1.0(@types/react@19.2.14)(react@19.2.5):
-    optionalDependencies:
-      '@types/react': 19.2.14
-      react: 19.2.5
-
   reselect@5.1.1: {}
 
   resolve-from@4.0.0: {}
@@ -6456,6 +6463,10 @@ snapshots:
     dependencies:
       queue-microtask: 1.2.3
 
+  rxjs@7.8.2:
+    dependencies:
+      tslib: 2.8.1
+
   safe-array-concat@1.1.3:
     dependencies:
       call-bind: 1.0.8
@@ -6657,8 +6668,6 @@ snapshots:
 
   swiper@12.1.3: {}
 
-  symbol-observable@4.0.0: {}
-
   tailwind-merge@3.4.0: {}
 
   tailwind-variants@3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.2.2):
@@ -6684,10 +6693,6 @@ snapshots:
     dependencies:
       typescript: 5.9.3
 
-  ts-invariant@0.10.3:
-    dependencies:
-      tslib: 2.8.1
-
   ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
@@ -6890,12 +6895,6 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
-  zen-observable-ts@1.2.5:
-    dependencies:
-      zen-observable: 0.8.15
-
-  zen-observable@0.8.15: {}
-
   zod-validation-error@4.0.2(zod@4.3.6):
     dependencies:
       zod: 4.3.6

+ 6 - 6
src/app/(public)/category/[collection]/page.tsx

@@ -14,14 +14,14 @@ import {
   GET_FILTER_PRODUCTS,
   GET_TREE_CATEGORIES,
 } from "@/graphql";
-import { cachedGraphQLRequest, cachedCategoryRequest } from "@/utils/hooks/useCache";
+import { cachedGraphQLRequest, cachedCategoryRequest, getFilterAttributes } from "@/utils/hooks/useCache";
 import { SortByFields } from "@utils/constants";
 import { CategoryDetail } from "@components/theme/search/CategoryDetail";
 import { Suspense } from "react";
 import FilterListSkeleton from "@components/common/skeleton/FilterSkeleton";
 import { TreeCategoriesResponse } from "@/types/theme/category-tree";
 import { MobileSearchBar } from "@components/layout/navbar/MobileSearch";
-import { extractNumericId, findCategoryBySlug, getFilterAttributes, buildProductFilters } from "@utils/helper";
+import { extractNumericId, findCategoryBySlug, buildProductFilters } from "@utils/helper";
 
 /**列表页 */
 export async function generateMetadata({
@@ -31,7 +31,7 @@ export async function generateMetadata({
 }): Promise<Metadata> {
   const { collection: categorySlug } = await params;
 
-  const treeData = await cachedGraphQLRequest<TreeCategoriesResponse>(
+  const {data:treeData} = await cachedGraphQLRequest<TreeCategoriesResponse>(
     "category",
     GET_TREE_CATEGORIES,
     { parentId: 1 }
@@ -60,7 +60,7 @@ export default async function CategoryPage({
   const { collection: categorySlug } = await params;
   const resolvedParams = await searchParams;
 
-  const [treeData, filterAttributes] = await Promise.all([
+  const [{data:treeData}, filterAttributes] = await Promise.all([
     cachedGraphQLRequest<TreeCategoriesResponse>(
       "category",
       GET_TREE_CATEGORIES,
@@ -103,7 +103,7 @@ export default async function CategoryPage({
 
   const filterInput = JSON.stringify(filterObject);
   
-  const [data] = await Promise.all([
+  const [{data}] = await Promise.all([
     cachedCategoryRequest<ProductsResponse>(
       categorySlug,
       GET_FILTER_PRODUCTS,
@@ -121,7 +121,7 @@ export default async function CategoryPage({
 
   const products = data?.products?.edges?.map((e) => e.node) || [];
   const pageInfo = data?.products?.pageInfo;
-  const totalCount = data?.products?.totalCount;
+  const totalCount = data?.products?.totalCount || 0;
   const translation = categoryItem.translation;
 
   return (

+ 2 - 2
src/app/(public)/page.tsx

@@ -8,7 +8,7 @@ export const revalidate = 3600;
 
 export default async function Home() {
   // const 
-  const data = await cachedGraphQLRequest<ThemeCustomizationResponse>(
+  const {data} = await cachedGraphQLRequest<ThemeCustomizationResponse>(
     "home",
     GET_THEME_CUSTOMIZATION,
     { first: 20 }
@@ -18,7 +18,7 @@ export default async function Home() {
     <>
       <HomeImageBanner />
       <DatePicker label={"Birth date"} labelPlacement={"outside"} />
-      <RenderThemeCustomization themeCustomizations={data?.themeCustomizations} />
+      <RenderThemeCustomization themeCustomizations={data?.themeCustomizations ?? {edges: []}} />
     </>
     
   );

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

@@ -34,7 +34,7 @@ import Link from "next/link";
 async function getSingleProduct(urlKey: string) {
 
   try {
-    const dataById = await cachedProductRequest<SingleProductResponse>(
+    const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
       urlKey, // 产品名称
       GET_PRODUCT_BY_URL_KEY, // gql查询语句
       { urlKey: urlKey },

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

@@ -357,7 +357,7 @@ export function ProductInformation({
         if(action === 'buynow') {
             if(res) {
                 const responseData = res.data?.createAddProductInCart?.addProductInCart;
-                if(responseData.success) {
+                if(responseData && responseData.success) {
                     redirect('/checkout?step=address', RedirectType.push);
                 }
             }

+ 7 - 8
src/app/(public)/search/page.tsx

@@ -4,10 +4,9 @@ import NotFound from "@/components/theme/search/not-found";
 import { isArray } from "@/utils/type-guards";
 import { GET_FILTER_PRODUCTS } from "@/graphql";
 import { GET_PRODUCTS, GET_PRODUCTS_PAGINATION } from "@/graphql";
-import { cachedGraphQLRequest } from "@/utils/hooks/useCache";
+import { cachedGraphQLRequest, getFilterAttributes } from "@/utils/hooks/useCache";
 import {
   generateMetadataForPage,
-  getFilterAttributes,
   buildProductFilters,
 } from "@/utils/helper";
 import SortOrder from "@/components/theme/filters/SortOrder";
@@ -31,7 +30,7 @@ export async function generateStaticParams() {
     const commonSearches = [""];
     const params = [];
     for (const query of commonSearches) {
-      const data = await cachedGraphQLRequest<ProductsResponse>(
+      const {data} = await cachedGraphQLRequest<ProductsResponse>(
         "search",
         GET_PRODUCTS,
         {
@@ -55,7 +54,7 @@ export async function generateStaticParams() {
         }
         params.push(pageParams);
         if (i < totalPages - 1) {
-          const pageData = await cachedGraphQLRequest<ProductsResponse>(
+          const {data: pageData} = await cachedGraphQLRequest<ProductsResponse>(
             "search",
             GET_PRODUCTS,
             {
@@ -137,9 +136,9 @@ export default async function SearchPage({
     );
   } else {
     dataPromise = (async () => {
-      let currentAfterCursor = afterCursor;
+      let currentAfterCursor: string | undefined = afterCursor;
       if (currentPage > 0 && !afterCursor) {
-        const cursorData = await cachedGraphQLRequest<ProductsResponse>(
+        const {data: cursorData} = await cachedGraphQLRequest<ProductsResponse>(
           "search",
           GET_PRODUCTS_PAGINATION,
           {
@@ -163,14 +162,14 @@ export default async function SearchPage({
     })();
   }
 
-  const [data, filterAttributes] = await Promise.all([
+  const [{data}, filterAttributes] = await Promise.all([
     dataPromise,
     getFilterAttributes(),
   ]);
 
   const products = data?.products?.edges?.map((e) => e.node) || [];
   const pageInfo = data?.products?.pageInfo;
-  const totalCount = data?.products?.totalCount;
+  const totalCount = data?.products?.totalCount || 0;
 
   return (
     <>

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

@@ -1,6 +1,6 @@
 import { GET_RELATED_PRODUCTS } from "@/graphql";
 import { ProductsSection } from "./ProductsSection";
-import { SingleProductResponse } from "@/app/(public)/product/[...urlProduct]/page";
+import { SingleProductResponse } from "@components/catalog/type";
 import { cachedProductRequest } from "@/utils/hooks/useCache";
 
 export async function RelatedProductsSection({
@@ -10,7 +10,7 @@ export async function RelatedProductsSection({
 }) {
     async function getRelatedProduct(urlKey: string) {
       try {
-        const dataById = await cachedProductRequest<SingleProductResponse>(
+        const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
           urlKey,
           GET_RELATED_PRODUCTS,
           {

+ 3 - 2
src/components/checkout/stepper/payment/index.tsx

@@ -1,11 +1,12 @@
 "use client";
 
 import { CartCheckoutPageSkeleton } from "@/components/common/skeleton/CheckoutSkeleton";
-import { useQuery } from "@apollo/client";
+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?: {
@@ -15,7 +16,7 @@ const Payment: FC<{
   currentStep?: string;
 }> = ({ selectedPayment, currentStep }) => {
   const token = getCartToken();
-  const { data, loading: isLoading } = useQuery(GET_CHECKOUT_PAYMENT_METHODS, {
+  const { data, loading: isLoading } = useQuery<CheckoutPaymentMethodsData>(GET_CHECKOUT_PAYMENT_METHODS, {
     variables: { token: token || "" },
     skip: !token,
     fetchPolicy: "cache-first",

+ 3 - 3
src/components/checkout/stepper/shipping/index.tsx

@@ -1,10 +1,10 @@
 "use client";
 
 import { CartCheckoutPageSkeleton } from "@/components/common/skeleton/CheckoutSkeleton";
-import { useQuery } from "@apollo/client";
+import { useQuery } from "@apollo/client/react";
 import ShippingMethod from "./ShippingMethod";
 import { FC } from "react";
-import { SelectedShippingRateType } from "@/types/checkout/type";
+import { SelectedShippingRateType, GetCheckoutShippingRatesData } from "@/types/checkout/type";
 import { GET_CHECKOUT_SHIPPING_RATES } from "@/graphql";
 import { getCartToken } from "@/utils/getCartToken";
 
@@ -14,7 +14,7 @@ const Shipping: FC<{
 }> = ({ selectedShippingRate, currentStep }) => {
   const token = getCartToken();
 
-  const { data, loading: isLoading } = useQuery(GET_CHECKOUT_SHIPPING_RATES, {
+  const { data, loading: isLoading } = useQuery<GetCheckoutShippingRatesData>(GET_CHECKOUT_SHIPPING_RATES, {
     variables: { token: token || "" },
     skip: !token,
     fetchPolicy: "cache-first",

+ 1 - 1
src/components/home/CategoryCarousel.tsx

@@ -72,7 +72,7 @@ const CategoryCarousel: FC<CategoryCarouselProps> = async ({
   options: _options,
 }) => {
   try {
-    const data = await cachedGraphQLRequest<CategoriesResponse>(
+    const {data} = await cachedGraphQLRequest<CategoriesResponse>(
       "home",
       GET_HOME_CATEGORIES,
       {}

+ 1 - 1
src/components/home/ProductCarousel.tsx

@@ -43,7 +43,7 @@ const ProductCarousel: FC<ProductCarouselProps> = async ({
       reverse = true;
     }
 
-    const data = await cachedGraphQLRequest<any>(
+    const {data} = await cachedGraphQLRequest<any>(
       "home",
       GET_PRODUCTS,
       {

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

@@ -15,7 +15,7 @@ import { TreeCategoriesResponse } from "@/types/theme/category-tree";
 
 export default async function Navbar() {
 
-  const data = await cachedGraphQLRequest<TreeCategoriesResponse>(
+  const {data} = await cachedGraphQLRequest<TreeCategoriesResponse>(
       "category",
       GET_TREE_CATEGORIES,
       { parentId: 1 }

+ 0 - 1
src/graphql/cart/mutations/AddProductToCart.ts

@@ -19,7 +19,6 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
         id
         cartToken
         subtotal
-        itemsCount
         taxAmount
         subtotal
         shippingAmount

+ 2 - 0
src/graphql/checkout/mutations/CreateCheckoutOrder.ts

@@ -8,6 +8,8 @@ export const CREATE_CHECKOUT_ORDER = gql`
       checkoutOrder {
         id
         orderId
+        message
+        success
       }
     }
   }

+ 1 - 0
src/graphql/checkout/queries/GetCheckoutPaymentMethods.ts

@@ -4,6 +4,7 @@ export const GET_CHECKOUT_PAYMENT_METHODS = gql`
   query CheckoutPaymentMethods {
     collectionPaymentMethods {
       id
+      _id
       method
       title
       description

+ 1 - 1
src/graphql/index.ts

@@ -6,4 +6,4 @@ export * from "./cart/mutations";
 export * from "./checkout/queries";
 export * from "./checkout/mutations";
 export * from "./types";
-export { graphqlRequest, graphqlRequestNoCache } from "../lib/graphql-fetch";
+

+ 79 - 0
src/lib/ApolloClientBrowser.ts

@@ -0,0 +1,79 @@
+import { cache as reactCache } from "react";
+import { HttpLink,ApolloLink } from "@apollo/client";
+import { SetContextLink } from "@apollo/client/link/context";
+import {
+  ApolloClient,
+  InMemoryCache,
+} from "@apollo/client-integration-nextjs";
+import { getSession } from "next-auth/react";
+import { getCartToken } from "@/utils/getCartToken";
+import { BagistoSession } from "@/types/types";
+
+// 这里注册的是客户端使用的apollo client
+
+let sessionCache: { session: BagistoSession | null; timestamp: number } | null = null;
+const SESSION_CACHE_TTL = 5000;
+
+const getSessionForRequest = reactCache(async () => {
+  return (await getSession()) as BagistoSession | null;
+});
+
+async function getCachedSession(): Promise<BagistoSession | null> {
+  if (typeof window === "undefined") {
+    return getSessionForRequest();
+  }
+
+  const now = Date.now();
+
+  if (sessionCache && now - sessionCache.timestamp < SESSION_CACHE_TTL) {
+    return sessionCache.session;
+  }
+  const session = (await getSession()) as BagistoSession | null;
+  sessionCache = { session, timestamp: now };
+  return session;
+}
+
+export default function makeClient() {
+    const httpLink = new HttpLink({
+        uri: "/api/graphql",
+        credentials: "include",
+
+        /*fetchOptions: {
+            // Optional: Next.js-specific fetch options
+            // Note: This doesn't work with `export const dynamic = "force-static"`
+        },*/
+  });
+
+
+  const authLink = new SetContextLink(async (prevContext, operation) => {
+      
+      const session = await getCachedSession();
+      const userToken = session?.user?.accessToken;
+      const guestToken = !userToken ? getCartToken() : null;
+      const token = userToken || guestToken;
+  
+      return {
+        headers: {
+          ...prevContext.headers,
+          ...(token && { Authorization: `Bearer ${token}` }),
+          "Content-Type": "application/json",
+        },
+      };
+    });
+
+    const link = ApolloLink.from([authLink, httpLink]);
+    return new ApolloClient({
+        // ssrMode,
+        link,
+        cache: new InMemoryCache(),
+        // defaultOptions: {
+        //     watchQuery: {
+        //         fetchPolicy: "cache-first",
+        //         nextFetchPolicy: "cache-first",
+        //     },
+        //     query: {
+        //         fetchPolicy: "cache-first",
+        //     },
+        // },
+    });
+}

+ 47 - 0
src/lib/ApolloClientServer.ts

@@ -0,0 +1,47 @@
+import { GRAPHQL_URL } from "@/utils/constants";
+import { HttpLink,ApolloLink } from "@apollo/client";
+import { SetContextLink } from "@apollo/client/link/context";
+import {
+  registerApolloClient,
+  ApolloClient,
+  InMemoryCache,
+} from "@apollo/client-integration-nextjs";
+
+// apollo client 底层还是调的nextjs的fetch函数。
+// apollo client 的缓存策略是建立在nextjs缓存策略之上的,相当于两个沙盒
+// 这里注册的是服务端的apollo client。项目中服务端使用的apollo client只是用于query 查询,而且不需要Authorization
+
+export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
+
+    const httpLink = new HttpLink({
+        uri: GRAPHQL_URL,
+        credentials: "include",
+        /*
+      fetchOptions: {
+        // Optional: Next.js-specific fetch options for caching and revalidation
+        // See: https://nextjs.org/docs/app/api-reference/functions/fetch
+      },
+      */
+    });
+    const authLink = new SetContextLink((prevContext, operation) => {
+        const storefrontKey =
+            process.env.BAGISTO_STOREFRONT_KEY ||
+            process.env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY ||
+            "";
+
+        return {
+            headers: {
+            ...prevContext.headers,
+            "X-STOREFRONT-KEY": storefrontKey,
+            },
+        };
+
+    });
+
+    const link = ApolloLink.from([authLink, httpLink]);
+
+  return new ApolloClient({
+    cache: new InMemoryCache(),
+    link: link
+  });
+});

+ 97 - 52
src/lib/graphql-fetch.ts

@@ -1,14 +1,61 @@
 import { unstable_cache } from "next/cache";
 import { print, type DocumentNode } from "graphql";
 import {
-  type ApolloQueryResult,
   type OperationVariables,
+  ApolloClient
 } from "@apollo/client";
-import makeClient from "./apollo-client";
 
+import {getClient} from "@/lib/ApolloClientServer";
 
+import {
+  CombinedGraphQLErrors,
+  CombinedProtocolErrors,
+  LocalStateError,
+  ServerError,
+  ServerParseError,
+  UnconventionalError,
+} from "@apollo/client/errors";
+// Comprehensive error handling example. https://www.apollographql.com/docs/react/data/error-handling
+function handleError(error: unknown) {
+  let res = '';
+  if (CombinedGraphQLErrors.is(error)) {
+    // Handle GraphQL errors 发起请求前graphql的致命错误 直接抛出
+    throw error;
+  } /*else if (CombinedProtocolErrors.is(error)) {
+    // Handle multipart subscription protocol errors
+    error.errors.forEach((protocolError) => {
+      console.log(protocolError.message);
+      console.log(protocolError.extensions);
+    });
+  } */else if (LocalStateError.is(error)) { // 使用@client 报错时
+    // Handle errors thrown by the `LocalState` class
+    throw error;
+
+  } else if (ServerError.is(error)) {
+    // Handle server HTTP errors
+    res = `Response body: ${error.statusCode} / ${error.message}`;
+    // Handle unauthorized access
+    //  if (error.statusCode === 401) {
+    //    
+    //  }
+  } else if (ServerParseError.is(error)) {
+    // Handle JSON parse 
+     throw error;
+  } else if (UnconventionalError.is(error)) {
+    // Handle errors thrown by irregular types
+    throw error;
 
+  } else {
+    // Handle other errors
+    throw error;
+  }
+  return res;
+}
 
+export interface GraphqlRequestResult<TData = unknown> {
+  data: TData | null;
+  error: string | undefined;
+}
 
 export type CacheLifePreset =
   | "seconds"
@@ -46,7 +93,7 @@ export function getRevalidateTime(
 
 
 
-const queryPrintMemo = new WeakMap<DocumentNode, string>();
+
 
 export function stableStringify(value: unknown): string {
   if (value === null || typeof value !== "object") {
@@ -88,18 +135,44 @@ export async function graphqlRequest<
   query: DocumentNode,
   variables?: TVariables,
   options?: GraphQLRequestOptions
-): Promise<TData> {
+): Promise<GraphqlRequestResult<TData>> {
+  const client = getClient();
+  let resData, resError;
+  const revalidate = getRevalidateTime(options?.life);
+  let queryOption: ApolloClient.QueryOptions<TData> = {
+    query,
+    variables,
+    fetchPolicy: "network-only",
+    context: {
+      fetchOptions: {
+        next: {
+          revalidate,
+          tags: options?.tags,
+        },
+      },
+    }
+  };
+
   if (options?.noCache) {
-    const client = makeClient();
-    const result: ApolloQueryResult<TData> =
-      await client.query({
-        query,
-        variables,
-        context: options?.context,
-        fetchPolicy: options?.fetchPolicy ?? "no-cache",
-      });
-
-    return result.data;
+    /***
+     * client.query的参数是一个对象:
+     * {query, variables, context, fetchPolicy, errorPolicy}
+     * context.fetchOptions 可以设置nextjs fetch的缓存策略 https://www.apollographql.com/docs/react/integrations/nextjs
+     * context: {
+        fetchOptions: {
+          next: {
+            revalidate: 60,      // 对应 life: 'minutes'
+            tags: ['posts'],     // 对应 tags 选项
+          },
+        },
+      },
+     */
+    queryOption = {
+      query,
+      variables,
+      context: options?.context,
+      fetchPolicy: "no-cache", // 跳过 apollo client 的缓存,直接调fetch
+    };
   }
 
   if (options?.context) {
@@ -107,44 +180,16 @@ export async function graphqlRequest<
       "graphqlRequest: Caching with `context` is unsafe. Use noCache instead."
     );
   }
-
-  let queryString: string;
-  const cachedQueryString = queryPrintMemo.get(query);
-
-  if (cachedQueryString) {
-    queryString = cachedQueryString;
-  } else {
-    queryString = print(query);
-    queryPrintMemo.set(query, queryString);
+  try {
+      // Promise-based APIs (e.g. client.query, client.mutate) - Errors either reject the promise or are returned in the result as the error field.
+      // 如果错误被reject 则会进入catch
+      const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
+      console.log('graphqlRequest network-only result ---- ', result);
+      resData = result.data || null;
+      return {data: resData, error: result.error? result.error.message : '' };
+  } catch (error) {
+    throw error;
   }
-
-  const cacheKey = `graphql:${stableStringify({
-    query: queryString,
-    variables,
-  })}`;
-
-  const revalidate = getRevalidateTime(options?.life);
-
-  const cachedQuery = unstable_cache(
-    async (): Promise<TData> => {
-      const client = makeClient();
-      const result: ApolloQueryResult<TData> =
-        await client.query({
-          query,
-          variables,
-          fetchPolicy: "network-only",
-        });
-
-      return result.data;
-    },
-    [cacheKey],
-    {
-      tags: options?.tags,
-      revalidate,
-    }
-  );
-
-  return cachedQuery();
 }
 
 
@@ -159,7 +204,7 @@ export async function graphqlRequestNoCache<
     GraphQLRequestOptions,
     "noCache" | "tags" | "life"
   >
-): Promise<TData> {
+): Promise<GraphqlRequestResult<TData>> {
   return graphqlRequest<TData, TVariables>(
     query,
     variables,

+ 4 - 4
src/providers/ApolloWrapper.tsx

@@ -1,13 +1,13 @@
 "use client";
 
-import { ApolloProvider } from "@apollo/client/react";
+import { ApolloNextAppProvider } from "@apollo/client-integration-nextjs";
 import { ReactNode, useMemo } from "react";
-import initializeApollo  from "../lib/apollo-client";
+import makeClient from "@/lib/ApolloClientBrowser";
 
 const ApolloWrapper = ({ children }: { children: ReactNode }) => {
-  const client = useMemo(() => initializeApollo(), []);
+  // const client = useMemo(() => initializeApollo(), []);
   
-  return <ApolloProvider client={client}>{children}</ApolloProvider>;
+  return <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>;
 };
 
 export  {ApolloWrapper};

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

@@ -36,6 +36,7 @@ export interface ReadCart {
   grandTotal: number;
   shippingAmount: number;
   selectedShippingRate: string | null;
+  selectedShippingRateTitle: string | null;
   subtotal: number;
   itemsQty: number;
   isGuest: boolean;

+ 5 - 8
src/types/checkout/type.ts

@@ -1,12 +1,6 @@
 export type AddressType = "cart_billing" | "cart_shipping";
 
-export interface GetCheckoutAddressesResponse {
-  collectionGetCheckoutAddresses: CheckoutAddressConnection;
-}
 
-export interface CheckoutAddressConnection {
-  edges: CheckoutAddressEdge[];
-}
 
 
 
@@ -155,6 +149,8 @@ export interface GetCheckoutShippingRatesOperation {
 export interface CheckoutOrder {
   id: string;
   orderId: string;
+  message: string;
+  success: boolean;
 }
 export interface CreateCheckoutOrderPayload {
   checkoutOrder: CheckoutOrder;
@@ -184,11 +180,12 @@ export interface CheckoutAddress {
   useForShipping: boolean;
 }
 
-export interface CheckoutAddressesConnection {
+
+export interface CheckoutAddressConnection {
   edges: CheckoutAddressEdge[];
 }
 export interface GetCheckoutAddressesData {
-  collectionGetCheckoutAddresses: CheckoutAddressesConnection;
+  collectionGetCheckoutAddresses: CheckoutAddressConnection;
 }
 export interface GetCheckoutAddressesOperation {
   data: GetCheckoutAddressesData;

+ 1 - 1
src/utils/bagisto/index.ts

@@ -442,7 +442,7 @@ export async function subscribeUser(
 
 export async function getThemeCustomization(): Promise<ThemeCustomizationResult> {
   try {
-    const [footerRes, servicesRes] = await Promise.all([
+    const [{data: footerRes}, {data:servicesRes}] = await Promise.all([
       cachedGraphQLRequest<GetFooterResponse>("static", GET_FOOTER, {
         type: "footer_links",
       }),

+ 7 - 34
src/utils/helper.ts

@@ -5,8 +5,6 @@ import { isArray } from "./type-guards";
 import { BASE_URL, baseUrl } from "./constants";
 import { ProductData } from "@components/catalog/type";
 import { CategoryNode } from "@/types/theme/category-tree";
-import { cachedGraphQLRequest } from "./hooks/useCache";
-import { GET_FILTER_ATTRIBUTES } from "@/graphql";
 import { ProductReview } from "@/types/category/type";
 
 // Build revision identifier — emitted in <meta> for SSR cache validation.
@@ -24,11 +22,9 @@ export const createUrl = (
   return `${pathname}${queryString}`;
 };
 
-export const ensureStartsWith = (stringToCheck: string, startsWith: string) =>
-  stringToCheck.startsWith(startsWith)
-    ? stringToCheck
-    : `${startsWith}${stringToCheck}`;
-
+export const ensureStartsWith = (stringToCheck: string, startsWith: string) => {
+  return stringToCheck.startsWith(startsWith) ? stringToCheck : `${startsWith}${stringToCheck}`;
+}
 export const validateEnvironmentVariables = () => {
   const requiredEnvironmentVariables = ["BAGISTO_STORE_DOMAIN"];
   const missingEnvironmentVariables = [] as string[];
@@ -286,12 +282,12 @@ export async function generateMetadataForPage(
   };
 }
 
-export const parseCsv = (value?: string) =>
-  value
+export const parseCsv = (value?: string) => {
+  return value
     ?.split(",")
     .map((v) => v.trim())
     .filter(Boolean) ?? [];
-
+}
 /**
  * Safely converts a value to an array, handling null/undefined
  * @param value - Any value that might be an array, null, or undefined
@@ -403,30 +399,7 @@ export function safeParse<T = any>(value: string | null | undefined): T | null {
   }
 }
 
-/**
- * Fetches filter attributes (color, size, brand) for product filtering
- *
- * @returns Promise with formatted filter attributes
- */
-export async function getFilterAttributes() {
-  const filterData = await cachedGraphQLRequest<{
-    color: any;
-    size: any;
-    brand: any;
-  }>("static", GET_FILTER_ATTRIBUTES, { locale: "en" });
-
-  const attributes = [filterData?.color, filterData?.size, filterData?.brand];
-
-  return attributes.filter(Boolean).map((attr) => ({
-    id: attr.id,
-    code: attr.code,
-    adminName: attr.code.toUpperCase(),
-    options: attr.options.edges.map((o: any) => ({
-      id: o.node.id,
-      adminName: o.node.adminName,
-    })),
-  }));
-}
+
 
 /**
  * Parses URL search parameters and builds a filter object for product filtering

+ 1 - 1
src/utils/hooks/getProductReviews.ts

@@ -7,7 +7,7 @@ export async function getProductReviews(productId: string) {
   try {
     const variables = { product_id: Number(productId), first: 10 };
     
-    const response = await cachedProductRequest<any>(
+    const {data:response} = await cachedProductRequest<any>(
       productId,
       GET_PRODUCT_REVIEWS,
       variables

+ 1 - 1
src/utils/hooks/getProductSwatchAndReview.ts

@@ -5,7 +5,7 @@ import { cachedProductRequest } from "@/utils/hooks/useCache";
 
 export async function getProductWithSwatchAndReview(urlKey: string) {
   try {
-    const dataById = await cachedProductRequest<SingleProductResponse>(
+    const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
       urlKey,
       GET_PRODUCT_SWATCH_REVIEW,
       { urlKey: urlKey }

+ 5 - 4
src/utils/hooks/useAddToCart.ts

@@ -8,12 +8,13 @@ import { getCartToken } from "@utils/getCartToken";
 import { getCookie } from "@utils/cookie-tools";
 import { useGuestCartToken } from "./useGuestCartToken";
 import { IS_GUEST } from "@/utils/constants";
-import { useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 import {
   CREATE_ADD_PRODUCT_IN_CART,
   REMOVE_CART_ITEM,
   UPDATE_CART_ITEM,
 } from "@/graphql";
+import { AddToCartData, RemoveCartItemData } from "@/types/cart/type";
 
 
 
@@ -23,7 +24,7 @@ export const useAddProduct = () => {
   const { createGuestToken, resetGuestToken } = useGuestCartToken();
   const { showToast } = useCustomToast();
 
-  const [mutateAsync, { loading: isCartLoading }] = useMutation(
+  const [mutateAsync, { loading: isCartLoading }] = useMutation<AddToCartData>(
     CREATE_ADD_PRODUCT_IN_CART,
     {
       onCompleted: (res) => {
@@ -83,12 +84,12 @@ export const useAddProduct = () => {
 
     return {
       data: result.data,
-      error: result.errors,
+      error: result.error,
     };
   };
 
   //--------Remove Cart Product Quantity--------//
-  const [removeFromCart, { loading: isRemoveLoading }] = useMutation(
+  const [removeFromCart, { loading: isRemoveLoading }] = useMutation<RemoveCartItemData>(
     REMOVE_CART_ITEM,
     {
       onCompleted: async (response) => {

+ 3 - 3
src/utils/hooks/useAddress.ts

@@ -1,12 +1,12 @@
 "use client";
 
 import { GET_CHECKOUT_ADDRESSES } from "@/graphql";
-import { CheckoutAddressNode } from "@/types/checkout/type";
-import { useLazyQuery } from "@apollo/client";
+import { CheckoutAddressNode, GetCheckoutAddressesData } from "@/types/checkout/type";
+import { useLazyQuery } from "@apollo/client/react";
 import { useCallback } from "react";
 
 export const useAddress = () => {
-  const [fetchAddresses, { data, loading: isLoading, refetch }] = useLazyQuery(
+  const [fetchAddresses, { data, loading: isLoading, refetch }] = useLazyQuery<GetCheckoutAddressesData>(
     GET_CHECKOUT_ADDRESSES,
     {
       fetchPolicy: "cache-first",

+ 30 - 4
src/utils/hooks/useCache.ts

@@ -1,5 +1,6 @@
 import { type DocumentNode, type OperationVariables } from "@apollo/client";
-import { graphqlRequest, type CacheLifeOption } from "@/lib/graphql-fetch";
+import { graphqlRequest, type CacheLifeOption, GraphqlRequestResult } from "@/lib/graphql-fetch";
+import { GET_FILTER_ATTRIBUTES } from "@/graphql";
 
 export interface PageCacheConfig {
   tags: string[];
@@ -82,7 +83,7 @@ export async function cachedGraphQLRequest<
   page: keyof typeof PAGE_CACHE_CONFIG,
   query: DocumentNode,
   variables?: TVariables,
-): Promise<TData> {
+): Promise<GraphqlRequestResult<TData>> {
   const config = getPageCacheConfig(page);
   return graphqlRequest<TData, TVariables>(query, variables, config);
 }
@@ -97,7 +98,7 @@ export async function cachedProductRequest<
   productId: string,
   query: DocumentNode,
   variables?: TVariables,
-): Promise<TData> {
+): Promise<GraphqlRequestResult<TData>> {
   const config = getProductCacheConfig(productId);
   return graphqlRequest<TData, TVariables>(query, variables, config);
 }
@@ -112,7 +113,32 @@ export async function cachedCategoryRequest<
   categoryId: string,
   query: DocumentNode,
   variables?: TVariables,
-): Promise<TData> {
+): Promise<GraphqlRequestResult<TData>> {
   const config = getCategoryCacheConfig(categoryId);
   return graphqlRequest<TData, TVariables>(query, variables, config);
 }
+
+/**
+ * Fetches filter attributes (color, size, brand) for product filtering
+ *
+ * @returns Promise with formatted filter attributes
+ */
+export async function getFilterAttributes() {
+  const {data: filterData } = await cachedGraphQLRequest<{
+    color: any;
+    size: any;
+    brand: any;
+  }>("static", GET_FILTER_ATTRIBUTES, { locale: "en" });
+
+  const attributes = [filterData?.color, filterData?.size, filterData?.brand];
+
+  return attributes.filter(Boolean).map((attr) => ({
+    id: attr.id,
+    code: attr.code,
+    adminName: attr.code.toUpperCase(),
+    options: attr.options.edges.map((o: any) => ({
+      id: o.node.id,
+      adminName: o.node.adminName,
+    })),
+  }));
+}

+ 3 - 2
src/utils/hooks/useCartDetail.ts

@@ -1,11 +1,12 @@
 "use client";
 
-import { useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { addItem } from "@/store/slices/cart-slice";
 import { useCallback, useEffect, useState, useRef } from "react";
 import { GET_CART_ITEM } from "@/graphql";
 import { getCartToken } from "@/utils/getCartToken";
+import { GetCartItemData } from "@/types/cart/type";
 
 
 
@@ -16,7 +17,7 @@ export function useCartDetail() {
   const isInFlightRef = useRef(false);
 
   const [getCartDetailMutation, { data, loading: isLoading, error }] =
-    useMutation(GET_CART_ITEM, {
+    useMutation<GetCartItemData>(GET_CART_ITEM, {
       onCompleted: (response) => {
         const cartData = response?.createReadCart?.readCart;
         if (cartData) {

+ 6 - 5
src/utils/hooks/useCheckout.ts

@@ -1,4 +1,4 @@
-import { useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 import { useRouter } from "next/navigation";
 import { useCustomToast } from "./useToast";
 import { useDispatch } from "react-redux";
@@ -18,6 +18,7 @@ import {
   CREATE_CHECKOUT_SHIPPING_METHODS,
   GET_CHECKOUT_ADDRESSES,
 } from "@/graphql";
+import { CreateCheckoutShippingMethodData, CreateCheckoutPaymentMethodResponse, CreateCheckoutOrderData } from "@/types/checkout/type";
 
 export const useCheckout = () => {
   const router = useRouter();
@@ -53,7 +54,7 @@ export const useCheckout = () => {
     });
   };
 
-  const [saveShipping, { loading: isSaving }] = useMutation(
+  const [saveShipping, { loading: isSaving }] = useMutation<CreateCheckoutShippingMethodData>(
     CREATE_CHECKOUT_SHIPPING_METHODS,
     {
       onCompleted: (response) => {
@@ -84,7 +85,7 @@ export const useCheckout = () => {
     });
   };
 
-  const [savePayment, { loading: isPaymentLoading }] = useMutation(
+  const [savePayment, { loading: isPaymentLoading }] = useMutation<CreateCheckoutPaymentMethodResponse>(
     CREATE_CHECKOUT_PAYMENT_METHODS,
     {
       onCompleted: (response) => {
@@ -114,7 +115,7 @@ export const useCheckout = () => {
     });
   };
 
-  const [placeOrder, { loading: isPlaceOrder }] = useMutation(
+  const [placeOrder, { loading: isPlaceOrder }] = useMutation<CreateCheckoutOrderData>(
     CREATE_CHECKOUT_ORDER,
     {
       onCompleted: (response) => {
@@ -126,7 +127,7 @@ export const useCheckout = () => {
           router.replace("/success");
         } else {
           showToast(
-            responseData?.message || "Failed to place order",
+            "Failed to place order",
             "warning",
           );
         }

+ 3 - 2
src/utils/hooks/useMergeCart.ts

@@ -1,16 +1,17 @@
 "use client";
 
-import { useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 import { useAppDispatch } from "@/store/hooks";
 import { addItem } from "@/store/slices/cart-slice";
 import { CREATE_MERGE_CART } from "@/graphql";
 import { GUEST_CART_ID } from "@/utils/constants";
 import { setCookie } from "@utils/cookie-tools";
+import { CreateMergeCartData } from "@/types/cart/type";
 
 export function useMergeCart() {
   const dispatch = useAppDispatch();
 
-  const [mergeCart, { loading: isLoading }] = useMutation(CREATE_MERGE_CART, {
+  const [mergeCart, { loading: isLoading }] = useMutation<CreateMergeCartData>(CREATE_MERGE_CART, {
     onCompleted: (response) => {
       const responseData = response?.createMergeCart?.mergeCart;
       if (!responseData) {

+ 1 - 1
src/utils/hooks/useProductReview.ts

@@ -1,6 +1,6 @@
 "use client";
 
-import { useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 import { CREATE_PRODUCT_REVIEW } from "@/graphql";
 import { useCustomToast } from "./useToast";
 import { CreateProductReviewInput, ProductReviewResponse } from "@/types/review";