@doswiftly/cli 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +23 -323
  2. package/dist/commands/check.js +1 -1
  3. package/dist/commands/check.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +39 -20
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/doctor.js +3 -3
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/init.js +4 -4
  10. package/dist/commands/sdk.js +5 -5
  11. package/dist/commands/sdk.js.map +1 -1
  12. package/dist/commands/template.js +4 -4
  13. package/dist/commands/template.js.map +1 -1
  14. package/dist/commands/types.js +5 -5
  15. package/dist/commands/types.js.map +1 -1
  16. package/dist/commands/verify.js +2 -2
  17. package/dist/commands/verify.js.map +1 -1
  18. package/dist/lib/package-manager.d.ts +1 -1
  19. package/dist/lib/package-manager.js +1 -1
  20. package/package.json +1 -1
  21. package/templates/storefront-nextjs/README.md +16 -12
  22. package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
  23. package/templates/storefront-nextjs/app/account/page.tsx +2 -2
  24. package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
  25. package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
  26. package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
  27. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
  28. package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
  29. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
  30. package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
  31. package/templates/storefront-nextjs/app/page.tsx +1 -1
  32. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
  33. package/templates/storefront-nextjs/app/products/page.tsx +2 -2
  34. package/templates/storefront-nextjs/app/search/page.tsx +1 -1
  35. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
  36. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
  37. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
  38. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
  39. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
  40. package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
  41. package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
  42. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
  43. package/templates/storefront-nextjs/components/providers.tsx +1 -1
  44. package/templates/storefront-nextjs/lib/currency.tsx +3 -3
  45. package/templates/storefront-nextjs/lib/format.ts +1 -1
  46. package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
  47. package/templates/storefront-nextjs/package.dev.json +1 -1
  48. package/templates/storefront-nextjs/package.json +1 -1
  49. package/templates/storefront-nextjs/package.json.template +1 -1
  50. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
  51. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
  52. package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
  53. package/templates/storefront-nextjs-shadcn/README.md +29 -162
  54. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
  55. package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
  56. package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
  57. package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
  58. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
  59. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
  60. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
  61. package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
  62. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
  63. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
  64. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
  65. package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
  66. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
  67. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
  68. package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
  69. package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
  70. package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
  71. package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
  72. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
  73. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
  74. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
  75. package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
  76. package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
  77. package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
  78. package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
  79. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
  80. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
  81. package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
  82. package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
  83. package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
  84. package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
  85. package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
  86. package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
  87. package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
  88. package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
  89. package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
  90. package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
  91. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
  92. package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
  93. package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
  94. package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
  95. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
  96. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
  98. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
  99. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
  100. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
  101. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
  102. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
  103. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
  104. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
  105. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
  106. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
  107. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
  108. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
  109. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
  110. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
  111. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
  112. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
  113. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
  114. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
  115. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
  116. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
  117. package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
  118. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
  119. package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
  120. package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
  121. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
  122. package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
  123. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
  124. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
  125. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
  126. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
  127. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
  128. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
  129. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
  130. package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
  131. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
  132. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
  133. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
  134. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
  135. package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
  136. package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
  137. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
  138. package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
  139. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
  140. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
  141. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
  142. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
  143. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
  144. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
  145. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
  146. package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
  147. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
  148. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
  149. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
  150. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
  151. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
  152. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
  153. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
  154. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
  155. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
  156. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
  157. package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
  158. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
  159. package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
  160. package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
  161. package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
  162. package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
  163. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
  164. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
  165. package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
  166. package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
  167. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
  168. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
  169. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
  170. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
  171. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
  172. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
  173. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
  174. package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
  175. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
  176. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
  177. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
  178. package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
  179. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
  180. package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
  181. package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
  182. package/templates/storefront-nextjs-shadcn/package.json +12 -13
  183. package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
  184. package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
  185. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
  186. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
  187. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
  188. package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
  189. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
  190. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
  191. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
  192. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
  193. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
  194. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
  195. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
  196. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
  197. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
  198. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
  199. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
  200. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
  201. package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
  202. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
  203. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
  204. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
  205. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
  206. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
  207. package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
  208. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
  209. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
  210. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
@@ -1,9 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useRef } from 'react';
4
- import { useCartStore } from '@/stores/cart-store';
3
+ import { useCallback, useEffect, useRef } from 'react';
4
+ import { useQueryClient } from '@tanstack/react-query';
5
+ import { useCartStore, useCartStoreApi } from '@/stores/cart-store';
5
6
  import { useCartCreate, useCartLinesAdd, useCartLinesUpdate, useCartLinesRemove } from '@/lib/graphql/hooks';
7
+ import { queryKeys } from '@/lib/graphql/query-keys';
6
8
  import { toast } from 'sonner';
9
+ import { StorefrontError } from '@doswiftly/storefront-sdk';
7
10
 
8
11
  // Debounce delay for quantity updates (prevents rate limiting)
9
12
  const QUANTITY_UPDATE_DEBOUNCE_MS = 500;
@@ -33,6 +36,8 @@ const QUANTITY_UPDATE_DEBOUNCE_MS = 500;
33
36
  * ```
34
37
  */
35
38
  export function useCartActions() {
39
+ const queryClient = useQueryClient();
40
+ const cartStoreApi = useCartStoreApi();
36
41
  const {
37
42
  setCartId,
38
43
  clearCart,
@@ -54,7 +59,7 @@ export function useCartActions() {
54
59
  * Reads cartId from store (fresh, not stale closure) or creates a new cart.
55
60
  */
56
61
  const getOrCreateCartId = useCallback(async (forceNew: boolean = false): Promise<string> => {
57
- const currentCartId = useCartStore.getState().cartId;
62
+ const currentCartId = cartStoreApi.getState().cartId;
58
63
  if (currentCartId && !forceNew) {
59
64
  return currentCartId;
60
65
  }
@@ -73,19 +78,25 @@ export function useCartActions() {
73
78
  }
74
79
 
75
80
  throw new Error('Failed to create cart');
76
- } catch (error: any) {
81
+ } catch (error: unknown) {
77
82
  console.error('Cart creation failed:', error);
78
83
  throw error;
79
84
  }
80
- }, [setCartId, createCartMutation]);
85
+ }, [cartStoreApi, setCartId, createCartMutation]);
81
86
 
82
87
  /**
83
88
  * Check if error is a "Cart not found" error (stale/expired cart)
84
89
  */
85
- const isCartNotFoundError = (error: any): boolean => {
86
- const message = error?.message || '';
87
- return message.toLowerCase().includes('cart not found') ||
88
- message.toLowerCase().includes('cart does not exist');
90
+ const isCartNotFoundError = (error: unknown): boolean => {
91
+ if (error instanceof StorefrontError) {
92
+ const msg = error.message.toLowerCase();
93
+ return msg.includes('cart not found') || msg.includes('cart does not exist');
94
+ }
95
+ if (error instanceof Error) {
96
+ const msg = error.message.toLowerCase();
97
+ return msg.includes('cart not found') || msg.includes('cart does not exist');
98
+ }
99
+ return false;
89
100
  };
90
101
 
91
102
  /**
@@ -108,6 +119,9 @@ export function useCartActions() {
108
119
  const forceNewCart = _options?._forceNewCart ?? false;
109
120
 
110
121
  try {
122
+ // Cancel in-flight cart queries to prevent stale data overwriting
123
+ await queryClient.cancelQueries({ queryKey: queryKeys.cart.all() });
124
+
111
125
  const cartId = await getOrCreateCartId(forceNewCart);
112
126
 
113
127
  const result = await addLinesMutation.mutateAsync({
@@ -133,7 +147,7 @@ export function useCartActions() {
133
147
  // Open cart drawer to show the added item
134
148
  openCart();
135
149
  toast.success('Added to cart');
136
- } catch (error: any) {
150
+ } catch (error: unknown) {
137
151
  if (isCartNotFoundError(error) && !forceNewCart) {
138
152
  console.warn('Cart expired (caught), creating new cart and retrying...');
139
153
  setCartId(null);
@@ -141,7 +155,8 @@ export function useCartActions() {
141
155
  }
142
156
 
143
157
  console.error('Add to cart failed:', error);
144
- toast.error(error.message || 'Failed to add to cart');
158
+ const message = error instanceof Error ? error.message : 'Failed to add to cart';
159
+ toast.error(message);
145
160
  throw error;
146
161
  }
147
162
  }, [setCartId, openCart, getOrCreateCartId, addLinesMutation]);
@@ -151,7 +166,10 @@ export function useCartActions() {
151
166
  */
152
167
  const executeQuantityUpdate = useCallback(async (lineId: string, quantity: number) => {
153
168
  try {
154
- const currentCartId = useCartStore.getState().cartId;
169
+ // Cancel in-flight cart queries to prevent race conditions
170
+ await queryClient.cancelQueries({ queryKey: queryKeys.cart.all() });
171
+
172
+ const currentCartId = cartStoreApi.getState().cartId;
155
173
  if (!currentCartId) {
156
174
  throw new Error('No cart found');
157
175
  }
@@ -176,7 +194,7 @@ export function useCartActions() {
176
194
 
177
195
  throw new Error(errorMessage);
178
196
  }
179
- } catch (error: any) {
197
+ } catch (error: unknown) {
180
198
  if (isCartNotFoundError(error)) {
181
199
  console.warn('Cart expired during update (caught), clearing cart');
182
200
  clearCart();
@@ -185,9 +203,10 @@ export function useCartActions() {
185
203
  }
186
204
 
187
205
  console.error('Update quantity failed:', error);
188
- toast.error(error.message || 'Failed to update quantity');
206
+ const message = error instanceof Error ? error.message : 'Failed to update quantity';
207
+ toast.error(message);
189
208
  }
190
- }, [updateLinesMutation, clearCart]);
209
+ }, [cartStoreApi, queryClient, updateLinesMutation, clearCart]);
191
210
 
192
211
  /**
193
212
  * Remove item from cart by line ID.
@@ -196,7 +215,7 @@ export function useCartActions() {
196
215
  */
197
216
  const removeFromCart = useCallback(async (lineId: string) => {
198
217
  try {
199
- const currentCartId = useCartStore.getState().cartId;
218
+ const currentCartId = cartStoreApi.getState().cartId;
200
219
  if (!currentCartId) {
201
220
  return;
202
221
  }
@@ -219,7 +238,7 @@ export function useCartActions() {
219
238
  }
220
239
 
221
240
  toast.success('Removed from cart');
222
- } catch (error: any) {
241
+ } catch (error: unknown) {
223
242
  if (isCartNotFoundError(error)) {
224
243
  console.warn('Cart expired during remove (caught), clearing stale cartId');
225
244
  setCartId(null);
@@ -227,10 +246,11 @@ export function useCartActions() {
227
246
  }
228
247
 
229
248
  console.error('Remove from cart failed:', error);
230
- toast.error(error.message || 'Failed to remove from cart');
249
+ const message = error instanceof Error ? error.message : 'Failed to remove from cart';
250
+ toast.error(message);
231
251
  throw error;
232
252
  }
233
- }, [setCartId, removeLinesMutation]);
253
+ }, [cartStoreApi, setCartId, removeLinesMutation]);
234
254
 
235
255
  /**
236
256
  * Update item quantity (debounced, takes lineId).
@@ -266,6 +286,18 @@ export function useCartActions() {
266
286
  updateTimeoutRef.current.set(lineId, timeout);
267
287
  }, [removeFromCart, executeQuantityUpdate]);
268
288
 
289
+ // Cleanup pending debounce timeouts on unmount
290
+ useEffect(() => {
291
+ const timeouts = updateTimeoutRef.current;
292
+ return () => {
293
+ for (const timeout of timeouts.values()) {
294
+ clearTimeout(timeout);
295
+ }
296
+ timeouts.clear();
297
+ pendingUpdatesRef.current.clear();
298
+ };
299
+ }, []);
300
+
269
301
  /**
270
302
  * Clear entire cart.
271
303
  * Clears cartId in zustand persist → useCartSync returns empty.
@@ -3,6 +3,8 @@
3
3
  import { useEffect } from "react";
4
4
  import { useCart } from "@/lib/graphql/hooks";
5
5
  import { useCartStore } from "@/stores/cart-store";
6
+ import { useHydrated } from "@doswiftly/storefront-sdk/react";
7
+ import type { CartLineFields } from "@/lib/graphql/fragments";
6
8
 
7
9
  /**
8
10
  * Mapped cart item for display components.
@@ -31,18 +33,20 @@ export interface CartItemData {
31
33
  */
32
34
  export function useCartSync() {
33
35
  const cartId = useCartStore((state) => state.cartId);
34
- const isHydrated = useCartStore((state) => state.isHydrated);
35
36
  const setCartId = useCartStore((state) => state.setCartId);
37
+ const isHydrated = useHydrated();
36
38
 
37
- const { data, isLoading, error, refetch } = useCart(cartId, {
39
+ const { data, isLoading, isSuccess, error, refetch } = useCart(cartId, {
38
40
  enabled: isHydrated && Boolean(cartId),
39
41
  retry: false,
40
42
  });
41
43
 
42
44
  const cart = data?.cart;
43
45
 
44
- // Detect stale cart: cartId exists but server returns nothing
45
- const isStaleCart = isHydrated && Boolean(cartId) && !isLoading && !cart;
46
+ // Detect stale cart: cartId exists but server confirmed no cart (isSuccess + !cart).
47
+ // Using isSuccess instead of !isLoading prevents false positives when queries are
48
+ // cancelled by mutation optimistic updates (cancelled query → status='pending', not 'success').
49
+ const isStaleCart = isHydrated && Boolean(cartId) && isSuccess && !cart;
46
50
 
47
51
  // Auto-clear stale cartId
48
52
  useEffect(() => {
@@ -52,7 +56,7 @@ export function useCartSync() {
52
56
  }, [isStaleCart, setCartId]);
53
57
 
54
58
  // Map GraphQL lines to display-friendly items
55
- const items: CartItemData[] = (cart?.lines ?? []).map((line: any) => {
59
+ const items: CartItemData[] = (cart?.lines ?? []).map((line: CartLineFields) => {
56
60
  const merchandiseTitle = line.merchandise.title ?? "";
57
61
  // Hide generic variant names like "Default" or "Default Title"
58
62
  const isDefaultVariant = /^default(\s+title)?$/i.test(merchandiseTitle);
@@ -63,10 +67,10 @@ export function useCartSync() {
63
67
  lineId: line.id,
64
68
  variantId: line.merchandise.id,
65
69
  productId: line.productId || line.merchandise.id,
66
- productHandle: line.productHandle,
70
+ productHandle: line.productHandle ?? undefined,
67
71
  productTitle,
68
72
  variantTitle,
69
- productType: line.productType,
73
+ productType: line.productType ?? undefined,
70
74
  quantity: line.quantity,
71
75
  price: {
72
76
  amount: line.merchandise.price.amount,
@@ -88,8 +92,8 @@ export function useCartSync() {
88
92
 
89
93
  // Discount data from server
90
94
  const discountCodes: string[] = (cart?.discountCodes ?? [])
91
- .filter((dc: any) => dc.applicable)
92
- .map((dc: any) => dc.code);
95
+ .filter((dc) => dc.applicable)
96
+ .map((dc) => dc.code);
93
97
  const totalDiscount = subtotal - total;
94
98
 
95
99
  return {
@@ -2,7 +2,7 @@
2
2
  * Auth Routes Configuration (SSOT)
3
3
  *
4
4
  * This file is the single source of truth for authentication routes.
5
- * Used by middleware.ts and can be imported by components if needed.
5
+ * Used by proxy.ts and can be imported by components if needed.
6
6
  *
7
7
  * IMPORTANT: Checkout is NOT protected to allow guest checkout (e-commerce best practice).
8
8
  * Users can optionally log in during checkout to use saved addresses.
@@ -16,6 +16,9 @@
16
16
  * export const guestOnlyRoutes = ['/auth/login', '/auth/register', '/auth/forgot-password'];
17
17
  */
18
18
 
19
+ // Re-export platform constants and matching utility from SDK
20
+ export { AUTH_COOKIE_NAME, matchesRoute } from '@doswiftly/storefront-sdk';
21
+
19
22
  /**
20
23
  * Routes that require authentication.
21
24
  * Unauthenticated users will be redirected to login.
@@ -30,12 +33,6 @@ export const protectedRoutes = ["/account"];
30
33
  */
31
34
  export const guestOnlyRoutes = ["/auth/login", "/auth/register"];
32
35
 
33
- /**
34
- * Cookie name for customer access token.
35
- * Must match the cookie name used by commerce-sdk.
36
- */
37
- export const AUTH_COOKIE_NAME = "customerAccessToken";
38
-
39
36
  /**
40
37
  * Default redirect paths
41
38
  */
@@ -45,13 +42,3 @@ export const redirects = {
45
42
  /** Where to redirect authenticated users trying to access guest-only routes */
46
43
  authenticated: "/account",
47
44
  } as const;
48
-
49
- /**
50
- * Check if a pathname matches any route in the list.
51
- * Supports both exact matches and prefix matches (e.g., /account matches /account/orders).
52
- */
53
- export function matchesRoute(pathname: string, routes: string[]): boolean {
54
- return routes.some(
55
- (route) => pathname === route || pathname.startsWith(`${route}/`)
56
- );
57
- }
@@ -1,109 +1,32 @@
1
1
  'use client';
2
2
 
3
- import { GraphQLClient } from 'graphql-request';
4
- import { getCurrencyFromCookie } from '@/lib/currency/';
5
-
6
- // Import config - will be injected by CLI during template generation
7
- let config: {
8
- shop: { slug: string };
9
- api: { url: string };
10
- } | null = null;
11
-
12
- try {
13
- // Dynamic import to handle cases where config doesn't exist yet
14
- const configModule = require('@/doswiftly.config');
15
- config = configModule.default || configModule;
16
- } catch (error) {
17
- // Fallback to environment variables if config file doesn't exist
18
- config = {
19
- shop: {
20
- slug: process.env.NEXT_PUBLIC_SHOP_SLUG || 'demo-shop',
21
- },
22
- api: {
23
- url: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
24
- },
25
- };
26
- }
27
-
28
- /**
29
- * Create a GraphQL client with dynamic currency header
30
- *
31
- * This factory creates a new client instance that reads the current
32
- * preferred currency from cookies on each request, ensuring SSR compatibility.
33
- *
34
- * The client injects:
35
- * - X-Shop-Slug: Shop identifier for multi-tenancy
36
- * - X-Preferred-Currency: User's preferred currency (from cookie, SSR-safe)
37
- *
38
- * @returns GraphQL client configured for client-side usage with currency support
39
- *
40
- * @example
41
- * ```typescript
42
- * const client = createGraphQLClient();
43
- * const data = await client.request(ProductDocument, { handle: 'my-product' });
44
- * ```
45
- */
46
- export function createGraphQLClient(): GraphQLClient {
47
- return new GraphQLClient(`${config!.api.url}/storefront/graphql`, {
48
- headers: () => {
49
- // Read current currency from cookie (SSR-safe, always fresh)
50
- const currency = getCurrencyFromCookie();
51
-
52
- return {
53
- 'X-Shop-Slug': config!.shop.slug,
54
- // Dynamic currency header - reads from cookie on each request
55
- ...(currency && { 'X-Preferred-Currency': currency }),
56
- };
57
- },
58
- });
59
- }
3
+ import { useStorefrontClient } from '@doswiftly/storefront-sdk/react';
4
+ import { useCallback } from 'react';
60
5
 
61
6
  /**
62
- * Singleton GraphQL client instance
63
- *
64
- * Reuses the same client instance across the application to avoid
65
- * creating multiple connections. The client's headers are evaluated
66
- * dynamically on each request, so currency changes are reflected
67
- * automatically.
68
- */
69
- let clientInstance: GraphQLClient | null = null;
70
-
71
- /**
72
- * Get or create the singleton GraphQL client
73
- *
74
- * Returns a cached client instance that automatically includes
75
- * the current preferred currency in request headers.
76
- *
77
- * The client is currency-aware and will include the user's
78
- * preferred currency in the X-Preferred-Currency header on
79
- * every request.
80
- *
81
- * @returns Singleton GraphQL client instance
82
- *
7
+ * Execute GraphQL operations using SDK's StorefrontClient from Context.
8
+ * Includes auth + currency middleware automatically.
9
+ *
10
+ * Replaces the old module-level singleton client which was incompatible
11
+ * with the Context-based store pattern.
12
+ *
83
13
  * @example
84
14
  * ```typescript
85
- * // In a Client Component
86
- * 'use client';
87
- * import { getGraphQLClient } from '@/lib/graphql/client';
88
- * import { ProductDocument } from '@/generated/graphql';
89
- *
90
- * export function ProductPrice({ handle }) {
91
- * const client = getGraphQLClient();
92
- *
93
- * const fetchProduct = async () => {
94
- * return client.request(ProductDocument, { handle });
95
- * };
96
- *
97
- * // Product will be fetched with current preferred currency
98
- * const { data } = useQuery(['product', handle], fetchProduct);
99
- *
100
- * return <div>{data.product.priceRange.minVariantPrice.amount}</div>;
15
+ * function useMyQuery() {
16
+ * const execute = useExecute();
17
+ * return useQuery({
18
+ * queryKey: ['my-query'],
19
+ * queryFn: () => execute<MyQueryResult>(MyDocument.toString(), variables),
20
+ * });
101
21
  * }
102
22
  * ```
103
23
  */
104
- export function getGraphQLClient(): GraphQLClient {
105
- if (!clientInstance) {
106
- clientInstance = createGraphQLClient();
107
- }
108
- return clientInstance;
24
+ export function useExecute() {
25
+ const client = useStorefrontClient();
26
+
27
+ return useCallback(
28
+ <TResult>(query: string, variables?: Record<string, unknown>): Promise<TResult> =>
29
+ client.query<TResult>(query, variables),
30
+ [client],
31
+ );
109
32
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Centralized GraphQL configuration.
3
+ *
4
+ * Single source of truth for API URL and shop slug.
5
+ * Used by both client.ts (browser) and server.ts (SSR).
6
+ *
7
+ * Resolution order:
8
+ * 1. doswiftly.config.ts (CLI-injected, highest priority)
9
+ * 2. Environment variables (NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SHOP_SLUG)
10
+ * 3. Defaults (localhost:8000, demo-shop)
11
+ */
12
+
13
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
14
+ const shopSlug = process.env.NEXT_PUBLIC_SHOP_SLUG || 'demo-shop';
15
+
16
+ let configApiUrl = apiUrl;
17
+ let configShopSlug = shopSlug;
18
+
19
+ try {
20
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
21
+ const configModule = require('@/doswiftly.config');
22
+ const config = configModule.default || configModule;
23
+ configApiUrl = config.api?.url || apiUrl;
24
+ configShopSlug = config.shop?.slug || shopSlug;
25
+ } catch {
26
+ // doswiftly.config.ts not available — use env vars
27
+ }
28
+
29
+ export const graphqlConfig = {
30
+ apiUrl: configApiUrl,
31
+ shopSlug: configShopSlug,
32
+ } as const;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Fragment type re-exports for component data contracts.
3
+ *
4
+ * Import fragment types from here instead of @/generated/graphql.
5
+ * Each fragment maps to a colocated .fragment.graphql file next to its component.
6
+ *
7
+ * @example
8
+ * import type { ProductCardFields } from '@/lib/graphql/fragments';
9
+ * interface Props { product: ProductCardFields }
10
+ */
11
+
12
+ // Template-local component fragments (colocated at components/)
13
+ export type { ProductCardFieldsFragment as ProductCardFields } from '@/generated/graphql';
14
+ export type { ProductDetailFieldsFragment as ProductDetailFields } from '@/generated/graphql';
15
+ export type { ProductVariantFieldsFragment as ProductVariantFields } from '@/generated/graphql';
16
+ export type { CollectionCardFieldsFragment as CollectionCardFields } from '@/generated/graphql';
17
+ export type { CartLineFieldsFragment as CartLineFields } from '@/generated/graphql';
18
+ export type { OrderSummaryFieldsFragment as OrderSummaryFields } from '@/generated/graphql';
19
+ export type { CustomerInfoFieldsFragment as CustomerInfoFields } from '@/generated/graphql';
20
+ export type { CategoryNodeFieldsFragment as CategoryNodeFields } from '@/generated/graphql';
21
+
22
+ // Backend SSOT fragments — lightweight price for listing views
23
+ export type { PriceFragment as Price } from '@/generated/graphql';
24
+
25
+ // Backend SSOT fragments (used by loyalty domain components)
26
+ // Same naming convention — clean names without Fragment suffix
27
+ export type { LoyaltyPointsSummaryFragment as LoyaltyPointsSummary } from '@/generated/graphql';
28
+ export type { LoyaltyTierFragment as LoyaltyTier } from '@/generated/graphql';
29
+ export type { TierProgressFragment as TierProgress } from '@/generated/graphql';
30
+ export type { LoyaltyMemberFragment as LoyaltyMember } from '@/generated/graphql';
31
+ export type { LoyaltyRewardFragment as LoyaltyReward } from '@/generated/graphql';
32
+ export type { LoyaltyTransactionFragment as LoyaltyTransaction } from '@/generated/graphql';
33
+ export type { LoyaltySettingsFragment as LoyaltySettings } from '@/generated/graphql';
34
+ export type { ReferralStatsFragment as ReferralStats } from '@/generated/graphql';