@kasuvia/sdk 0.0.8 → 0.0.9

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 (176) hide show
  1. package/README.md +42 -15
  2. package/dist/AddressAutocomplete-CjcqPElD.d.mts +46 -0
  3. package/dist/AddressAutocomplete-CvJHuRkf.d.ts +46 -0
  4. package/dist/components/index.d.mts +497 -0
  5. package/dist/components/index.d.ts +497 -0
  6. package/dist/components/index.js +4217 -0
  7. package/dist/components/index.js.map +1 -0
  8. package/dist/components/index.mjs +4194 -0
  9. package/dist/components/index.mjs.map +1 -0
  10. package/dist/config/server.d.mts +26 -0
  11. package/dist/config/server.d.ts +26 -0
  12. package/dist/config/server.js +38 -0
  13. package/dist/config/server.js.map +1 -0
  14. package/dist/config/server.mjs +13 -0
  15. package/dist/config/server.mjs.map +1 -0
  16. package/dist/hooks/index.d.mts +393 -160
  17. package/dist/hooks/index.d.ts +393 -160
  18. package/dist/hooks/index.js +2792 -2137
  19. package/dist/hooks/index.js.map +1 -1
  20. package/dist/hooks/index.mjs +2734 -2121
  21. package/dist/hooks/index.mjs.map +1 -1
  22. package/dist/index-CNW_LINy.d.mts +578 -0
  23. package/dist/index-CNW_LINy.d.ts +578 -0
  24. package/dist/{index-C1chUA3P.d.mts → index-F7N6UItS.d.mts} +1 -1
  25. package/dist/{index-C1chUA3P.d.ts → index-F7N6UItS.d.ts} +1 -1
  26. package/dist/index-NkI10J_E.d.mts +40 -0
  27. package/dist/index-NkI10J_E.d.ts +40 -0
  28. package/dist/index.d.mts +20437 -103
  29. package/dist/index.d.ts +20437 -103
  30. package/dist/index.js +2111 -1802
  31. package/dist/index.js.map +1 -1
  32. package/dist/index.mjs +2006 -1786
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/media.service-Cx7dQjzX.d.mts +1393 -0
  35. package/dist/media.service-mrBFJ2UQ.d.ts +1393 -0
  36. package/dist/services/index.d.mts +908 -2
  37. package/dist/services/index.d.ts +908 -2
  38. package/dist/services/index.js +1492 -1686
  39. package/dist/services/index.js.map +1 -1
  40. package/dist/services/index.mjs +1431 -1670
  41. package/dist/services/index.mjs.map +1 -1
  42. package/package.json +46 -6
  43. package/src/components/bespoke/BespokeAvailabilityBadge.tsx +32 -0
  44. package/src/components/bespoke/BespokeProgressTracker.tsx +58 -0
  45. package/src/components/bespoke/index.ts +3 -0
  46. package/src/components/booking/SlotPicker.tsx +97 -0
  47. package/src/components/booking/index.ts +1 -0
  48. package/src/components/cart/CartItem.tsx +81 -0
  49. package/src/components/cart/CartSidebar.tsx +115 -0
  50. package/src/components/cart/CartTypeMismatchBanner.tsx +55 -0
  51. package/src/components/cart/index.ts +3 -0
  52. package/src/components/index.ts +8 -0
  53. package/src/components/orders/DeliverableReviewer.tsx +139 -0
  54. package/src/components/orders/index.ts +1 -0
  55. package/src/components/product/ProductCard.tsx +350 -0
  56. package/src/components/product/ProductGrid.tsx +84 -0
  57. package/src/components/product/index.ts +2 -0
  58. package/src/components/shared/AddressAutocomplete.tsx +212 -0
  59. package/src/components/shared/AnalyticsScripts.tsx +56 -0
  60. package/src/components/shared/CookieConsentBanner.tsx +98 -0
  61. package/src/components/shared/ImageEditorModal.tsx +1066 -0
  62. package/src/components/shared/ImageViewerModal.tsx +598 -0
  63. package/src/components/shared/LucideReactIcon.tsx +52 -0
  64. package/src/components/shared/MapPinDrop.tsx +101 -0
  65. package/src/components/shared/MultiSelectPicker.tsx +193 -0
  66. package/src/components/shared/OfflineBanner.tsx +46 -0
  67. package/src/components/shared/SearchableInput.tsx +153 -0
  68. package/src/components/shared/SearchablePopover.tsx +166 -0
  69. package/src/components/shared/SmartImage.tsx +311 -0
  70. package/src/components/shared/TruncatedValue.tsx +27 -0
  71. package/src/components/shared/UnsavedChangesGuard.tsx +138 -0
  72. package/src/components/shared/index.ts +15 -0
  73. package/src/components/skeletons/CardSkeleton.tsx +26 -0
  74. package/src/components/skeletons/CartItemSkeleton.tsx +26 -0
  75. package/src/components/skeletons/OrderItemSkeleton.tsx +26 -0
  76. package/src/components/skeletons/ProductCardSkeleton.tsx +26 -0
  77. package/src/components/skeletons/TableSkeleton.tsx +42 -0
  78. package/src/components/skeletons/index.ts +5 -0
  79. package/src/config/index.ts +22 -4
  80. package/src/config/server.ts +45 -0
  81. package/src/context/sdk-context.tsx +5 -2
  82. package/src/generated/auth-openapi.ts +2935 -0
  83. package/src/generated/cart-openapi.ts +652 -0
  84. package/src/generated/cms-openapi.ts +1668 -0
  85. package/src/generated/crm-openapi.ts +986 -0
  86. package/src/generated/graphql.ts +126 -0
  87. package/src/generated/index.ts +11 -0
  88. package/src/generated/media-openapi.ts +613 -0
  89. package/src/generated/notification-openapi.ts +862 -0
  90. package/src/generated/payment-openapi.ts +2713 -0
  91. package/src/generated/store-openapi.ts +9510 -0
  92. package/src/graphql/operations/collections.graphql +32 -0
  93. package/src/graphql/operations/products.graphql +197 -0
  94. package/src/graphql/operations/wishlist.graphql +25 -0
  95. package/src/hooks/index.ts +9 -2
  96. package/src/hooks/use-auth.ts +163 -69
  97. package/src/hooks/use-bespoke-availability.ts +39 -22
  98. package/src/hooks/use-business-config.ts +51 -0
  99. package/src/hooks/use-cart.ts +310 -238
  100. package/src/hooks/use-cms.ts +107 -127
  101. package/src/hooks/use-collections.ts +35 -55
  102. package/src/hooks/use-consultations.ts +9 -136
  103. package/src/hooks/use-countries.ts +19 -28
  104. package/src/hooks/use-currencies.ts +8 -20
  105. package/src/hooks/use-customer-address.ts +64 -0
  106. package/src/hooks/use-feedback.ts +34 -0
  107. package/src/hooks/use-languages.ts +26 -39
  108. package/src/hooks/use-media-upload.ts +23 -0
  109. package/src/hooks/use-notification-stream.ts +88 -0
  110. package/src/hooks/use-notifications.ts +163 -0
  111. package/src/hooks/use-order-stream.ts +29 -29
  112. package/src/hooks/use-orders.ts +91 -73
  113. package/src/hooks/use-payment.ts +116 -84
  114. package/src/hooks/use-products.ts +65 -269
  115. package/src/hooks/use-push-notifications.ts +122 -0
  116. package/src/hooks/use-reviews.ts +175 -0
  117. package/src/hooks/use-service-booking.ts +109 -132
  118. package/src/hooks/use-states.ts +16 -30
  119. package/src/hooks/use-wishlist.ts +44 -69
  120. package/src/index.ts +15 -11
  121. package/src/services/auth.service.ts +422 -132
  122. package/src/services/bespoke.service.ts +69 -10
  123. package/src/services/cart.service.ts +155 -186
  124. package/src/services/cms.service.ts +41 -50
  125. package/src/services/collection.service.ts +25 -63
  126. package/src/services/config.service.ts +10 -21
  127. package/src/services/consultation.service.ts +36 -93
  128. package/src/services/feedback.service.ts +76 -0
  129. package/src/services/index.ts +7 -1
  130. package/src/services/legal.service.ts +62 -0
  131. package/src/services/logistics.service.ts +235 -53
  132. package/src/services/media.service.ts +115 -0
  133. package/src/services/notifications.service.ts +204 -20
  134. package/src/services/order.service.ts +498 -127
  135. package/src/services/payment.service.ts +149 -100
  136. package/src/services/product.service.ts +157 -814
  137. package/src/services/review.service.ts +211 -0
  138. package/src/services/service-booking.service.ts +198 -99
  139. package/src/services/wishlist.service.ts +27 -74
  140. package/src/types/auth/index.ts +161 -18
  141. package/src/types/business/index.ts +9 -1
  142. package/src/types/cms/index.ts +91 -6
  143. package/src/types/cms/legal.ts +18 -0
  144. package/src/types/consultations/index.ts +60 -68
  145. package/src/types/index.ts +5 -3
  146. package/src/types/media/index.ts +35 -3
  147. package/src/types/payments/index.ts +80 -16
  148. package/src/types/reviews/index.ts +116 -0
  149. package/src/types/services/index.ts +113 -38
  150. package/src/types/store/checkout.ts +53 -0
  151. package/src/types/store/index.ts +273 -52
  152. package/src/types/store/invoice.ts +31 -0
  153. package/src/types/store/shipping.ts +84 -3
  154. package/src/types/ui/index.ts +1 -1
  155. package/src/utils/checkout.ts +19 -3
  156. package/src/utils/cookieConsent.ts +34 -0
  157. package/src/utils/format.ts +408 -0
  158. package/src/utils/geolocation.ts +134 -0
  159. package/src/utils/graphql-url.ts +47 -0
  160. package/src/utils/guest-session.ts +59 -0
  161. package/src/utils/http-client.ts +147 -0
  162. package/src/utils/index.ts +11 -4
  163. package/src/utils/status-colors.ts +79 -0
  164. package/src/utils/token-refresh.ts +115 -0
  165. package/src/utils/validators.ts +13 -0
  166. package/src/utils/youtube.ts +18 -0
  167. package/dist/index-C5YO9gTl.d.mts +0 -507
  168. package/dist/index-C5YO9gTl.d.ts +0 -507
  169. package/dist/index-CefG7_xq.d.mts +0 -793
  170. package/dist/index-DeHCf3zI.d.ts +0 -793
  171. package/src/graphql/collections.graphql.ts +0 -48
  172. package/src/graphql/index.ts +0 -7
  173. package/src/graphql/products.graphql.ts +0 -860
  174. package/src/graphql/wishlist.graphql.ts +0 -34
  175. package/src/hooks/use-consultation-stream.ts +0 -142
  176. package/src/types/bespoke/index.ts +0 -39
@@ -3,6 +3,22 @@
3
3
  *
4
4
  * React Query hooks for CMS model and entry operations.
5
5
  * config and accessToken are optional — if omitted, they are read from SdkContext.
6
+ *
7
+ * REAL BUG FIXED (2026-09-21): every hook here called the service functions
8
+ * with only a BID, but kasuvia-cms's SDK routes require the business's
9
+ * internal UUID (see cms.service.ts's header comment) — resolved here via
10
+ * `useBusinessConfig()`. Also fixed `.entries`/`.total` → the real
11
+ * `.results`/`.count`/`.total_pages` response shape.
12
+ *
13
+ * REAL BUG FIXED (2026-09-22): every one of these five hooks wrapped
14
+ * useQuery/useMutation/useBusinessConfig in a try/catch that caught real
15
+ * setup failures (no SdkProvider/QueryClientProvider) and returned a
16
+ * fabricated "successful, empty" result instead (`entries: [], error:
17
+ * null`) — indistinguishable from "this CMS model genuinely has zero
18
+ * entries." Conditionally calling hooks inside a try block also risks
19
+ * breaking React's Rules of Hooks if the block throws partway through (the
20
+ * hook call count would differ between renders). No other hook in this SDK
21
+ * does this; removed to match.
6
22
  */
7
23
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
8
24
  import {
@@ -19,14 +35,15 @@ import type {
19
35
  UpdateCmsEntryParams,
20
36
  ListCmsEntriesParams,
21
37
  } from '../types/cms'
22
- import { logger } from '../utils'
23
38
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
39
+ import { useBusinessConfig } from './use-business-config'
24
40
 
25
41
  export interface UseCmsEntriesReturn {
26
42
  entries: CmsEntry[]
27
43
  total: number
28
44
  page: number
29
45
  page_size: number
46
+ total_pages: number
30
47
  isLoading: boolean
31
48
  error: Error | null
32
49
  refetch: () => void
@@ -58,33 +75,24 @@ export function useCmsEntries(
58
75
  params = maybeParams
59
76
  }
60
77
 
61
- try {
62
- const query = useQuery({
63
- queryKey: ['cms-entries', slug, params],
64
- queryFn: () => listCmsEntries(cfg, slug, params),
65
- enabled: !!slug,
66
- })
67
-
68
- return {
69
- entries: query.data?.entries || [],
70
- total: query.data?.total || 0,
71
- page: query.data?.page || 1,
72
- page_size: query.data?.page_size || 50,
73
- isLoading: query.isLoading,
74
- error: query.error as Error | null,
75
- refetch: query.refetch,
76
- }
77
- } catch (error) {
78
- logger.warn('[useCmsEntries] Called outside QueryClientProvider, returning empty list')
79
- return {
80
- entries: [],
81
- total: 0,
82
- page: 1,
83
- page_size: 50,
84
- isLoading: false,
85
- error: null,
86
- refetch: () => {},
87
- }
78
+ const businessConfig = useBusinessConfig(cfg)
79
+ const businessUuid = businessConfig.data?.business_id
80
+
81
+ const query = useQuery({
82
+ queryKey: ['cms-entries', slug, params],
83
+ queryFn: () => listCmsEntries(cfg, businessUuid as string, slug, params),
84
+ enabled: !!slug && !!businessUuid,
85
+ })
86
+
87
+ return {
88
+ entries: query.data?.results || [],
89
+ total: query.data?.count || 0,
90
+ page: query.data?.page || 1,
91
+ page_size: query.data?.page_size || 50,
92
+ total_pages: query.data?.total_pages || 0,
93
+ isLoading: query.isLoading || businessConfig.isLoading,
94
+ error: (query.error || businessConfig.error) as Error | null,
95
+ refetch: query.refetch,
88
96
  }
89
97
  }
90
98
 
@@ -121,27 +129,20 @@ export function useCmsEntry(
121
129
  entryId = modelSlugOrEntryId
122
130
  }
123
131
 
124
- try {
125
- const query = useQuery({
126
- queryKey: ['cms-entry', slug, entryId],
127
- queryFn: () => getCmsEntry(cfg, slug, entryId),
128
- enabled: !!slug && !!entryId,
129
- })
132
+ const businessConfig = useBusinessConfig(cfg)
133
+ const businessUuid = businessConfig.data?.business_id
130
134
 
131
- return {
132
- entry: query.data,
133
- isLoading: query.isLoading,
134
- error: query.error as Error | null,
135
- refetch: query.refetch,
136
- }
137
- } catch (error) {
138
- logger.warn('[useCmsEntry] Called outside QueryClientProvider, returning undefined')
139
- return {
140
- entry: undefined,
141
- isLoading: false,
142
- error: null,
143
- refetch: () => {},
144
- }
135
+ const query = useQuery({
136
+ queryKey: ['cms-entry', slug, entryId],
137
+ queryFn: () => getCmsEntry(cfg, businessUuid as string, slug, entryId),
138
+ enabled: !!slug && !!entryId && !!businessUuid,
139
+ })
140
+
141
+ return {
142
+ entry: query.data,
143
+ isLoading: query.isLoading || businessConfig.isLoading,
144
+ error: (query.error || businessConfig.error) as Error | null,
145
+ refetch: query.refetch,
145
146
  }
146
147
  }
147
148
 
@@ -153,30 +154,23 @@ export interface UseCreateCmsEntryReturn {
153
154
 
154
155
  export function useCreateCmsEntry(config?: SdkConfig, accessToken?: string | null, modelSlug?: string): UseCreateCmsEntryReturn {
155
156
  const ctx = useSdkContext()
156
- try {
157
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
158
- const queryClient = useQueryClient()
159
-
160
- const mutation = useMutation({
161
- mutationFn: (params: CreateCmsEntryParams) =>
162
- token && modelSlug ? createCmsEntry(cfg, token, modelSlug, params) : Promise.reject('No access token or modelSlug'),
163
- onSuccess: () => {
164
- if (modelSlug) queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
165
- },
166
- })
167
-
168
- return {
169
- createEntry: mutation.mutate,
170
- isLoading: mutation.isPending,
171
- error: mutation.error as Error | null,
172
- }
173
- } catch (error) {
174
- logger.warn('[useCreateCmsEntry] Called outside QueryClientProvider, returning no-op mutation')
175
- return {
176
- createEntry: () => {},
177
- isLoading: false,
178
- error: null,
179
- }
157
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
158
+ const businessConfig = useBusinessConfig(cfg)
159
+ const businessUuid = businessConfig.data?.business_id
160
+ const queryClient = useQueryClient()
161
+
162
+ const mutation = useMutation({
163
+ mutationFn: (params: CreateCmsEntryParams) =>
164
+ token && modelSlug && businessUuid ? createCmsEntry(cfg, businessUuid, token, modelSlug, params) : Promise.reject('No access token, modelSlug, or resolved business'),
165
+ onSuccess: () => {
166
+ if (modelSlug) queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
167
+ },
168
+ })
169
+
170
+ return {
171
+ createEntry: mutation.mutate,
172
+ isLoading: mutation.isPending,
173
+ error: mutation.error as Error | null,
180
174
  }
181
175
  }
182
176
 
@@ -188,34 +182,27 @@ export interface UseUpdateCmsEntryReturn {
188
182
 
189
183
  export function useUpdateCmsEntry(config?: SdkConfig, accessToken?: string | null, modelSlug?: string): UseUpdateCmsEntryReturn {
190
184
  const ctx = useSdkContext()
191
- try {
192
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
193
- const queryClient = useQueryClient()
194
-
195
- const mutation = useMutation({
196
- mutationFn: ({ entryId, params }: { entryId: string; params: UpdateCmsEntryParams }) =>
197
- token && modelSlug ? updateCmsEntry(cfg, token, modelSlug, entryId, params) : Promise.reject('No access token or modelSlug'),
198
- onSuccess: () => {
199
- if (modelSlug) {
200
- queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
201
- queryClient.invalidateQueries({ queryKey: ['cms-entry', modelSlug] })
202
- }
203
- },
204
- })
205
-
206
- return {
207
- updateEntry: (entryId: string, params: UpdateCmsEntryParams) =>
208
- mutation.mutate({ entryId, params }),
209
- isLoading: mutation.isPending,
210
- error: mutation.error as Error | null,
211
- }
212
- } catch (error) {
213
- logger.warn('[useUpdateCmsEntry] Called outside QueryClientProvider, returning no-op mutation')
214
- return {
215
- updateEntry: () => {},
216
- isLoading: false,
217
- error: null,
218
- }
185
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
186
+ const businessConfig = useBusinessConfig(cfg)
187
+ const businessUuid = businessConfig.data?.business_id
188
+ const queryClient = useQueryClient()
189
+
190
+ const mutation = useMutation({
191
+ mutationFn: ({ entryId, params }: { entryId: string; params: UpdateCmsEntryParams }) =>
192
+ token && modelSlug && businessUuid ? updateCmsEntry(cfg, businessUuid, token, modelSlug, entryId, params) : Promise.reject('No access token, modelSlug, or resolved business'),
193
+ onSuccess: () => {
194
+ if (modelSlug) {
195
+ queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
196
+ queryClient.invalidateQueries({ queryKey: ['cms-entry', modelSlug] })
197
+ }
198
+ },
199
+ })
200
+
201
+ return {
202
+ updateEntry: (entryId: string, params: UpdateCmsEntryParams) =>
203
+ mutation.mutate({ entryId, params }),
204
+ isLoading: mutation.isPending,
205
+ error: mutation.error as Error | null,
219
206
  }
220
207
  }
221
208
 
@@ -227,32 +214,25 @@ export interface UseDeleteCmsEntryReturn {
227
214
 
228
215
  export function useDeleteCmsEntry(config?: SdkConfig, accessToken?: string | null, modelSlug?: string): UseDeleteCmsEntryReturn {
229
216
  const ctx = useSdkContext()
230
- try {
231
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
232
- const queryClient = useQueryClient()
233
-
234
- const mutation = useMutation({
235
- mutationFn: (entryId: string) =>
236
- token && modelSlug ? deleteCmsEntry(cfg, token, modelSlug, entryId) : Promise.reject('No access token or modelSlug'),
237
- onSuccess: () => {
238
- if (modelSlug) {
239
- queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
240
- queryClient.invalidateQueries({ queryKey: ['cms-entry', modelSlug] })
241
- }
242
- },
243
- })
244
-
245
- return {
246
- deleteEntry: mutation.mutate,
247
- isLoading: mutation.isPending,
248
- error: mutation.error as Error | null,
249
- }
250
- } catch (error) {
251
- logger.warn('[useDeleteCmsEntry] Called outside QueryClientProvider, returning no-op mutation')
252
- return {
253
- deleteEntry: () => {},
254
- isLoading: false,
255
- error: null,
256
- }
217
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
218
+ const businessConfig = useBusinessConfig(cfg)
219
+ const businessUuid = businessConfig.data?.business_id
220
+ const queryClient = useQueryClient()
221
+
222
+ const mutation = useMutation({
223
+ mutationFn: (entryId: string) =>
224
+ token && modelSlug && businessUuid ? deleteCmsEntry(cfg, businessUuid, token, modelSlug, entryId) : Promise.reject('No access token, modelSlug, or resolved business'),
225
+ onSuccess: () => {
226
+ if (modelSlug) {
227
+ queryClient.invalidateQueries({ queryKey: ['cms-entries', modelSlug] })
228
+ queryClient.invalidateQueries({ queryKey: ['cms-entry', modelSlug] })
229
+ }
230
+ },
231
+ })
232
+
233
+ return {
234
+ deleteEntry: mutation.mutate,
235
+ isLoading: mutation.isPending,
236
+ error: mutation.error as Error | null,
257
237
  }
258
238
  }
@@ -1,13 +1,27 @@
1
1
  /**
2
2
  * Collections Hooks
3
- *
4
- * React Query hooks for fetching collections via Next.js API routes.
5
- * Client-side hooks fetch from API routes to avoid exposing backend URLs to browser.
3
+ *
4
+ * React Query hooks for fetching collections via the real GraphQL storefront API.
5
+ *
6
+ * REAL BUG FIXED (2026-09-22), live-confirmed: both hooks here fetched from
7
+ * `/api/collections` / `/api/collections/{slug}` — Next.js API routes that
8
+ * do not exist anywhere in kasuvia-storefront (`app/api/collections`
9
+ * confirmed absent). This is the exact same class of bug already found and
10
+ * fixed in use-products.ts ("every hook here used to fetch from a deleted
11
+ * Next.js API route") — this file was simply missed in that same pass. Two
12
+ * real, live consumers were affected: `app/collections/CollectionsClient.tsx`
13
+ * (the entire "browse all collections" page) and `HomeClient.tsx`'s four
14
+ * home-page collection rails — both have been silently 404ing on every
15
+ * real page load. Fixed the same way: call the SDK's real GraphQL service
16
+ * functions directly (collection.service.ts), resolving config from
17
+ * SdkContext the same way every other SDK hook does.
6
18
  */
7
19
  import { useQuery } from '@tanstack/react-query'
8
20
  import type { ListParams } from '../types/store'
9
21
  import { getCacheTier } from '../utils/cache'
10
- import { logger } from '../utils'
22
+ import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
23
+ import type { SdkConfig } from '../config'
24
+ import { getStorefrontCollections, getStorefrontCollectionBySlug } from '../services/collection.service'
11
25
 
12
26
  export const collectionKeys = {
13
27
  all: ['core', 'collections'] as const,
@@ -17,61 +31,27 @@ export const collectionKeys = {
17
31
 
18
32
  /**
19
33
  * Hook to fetch paginated collections for the storefront.
20
- * Fetches from Next.js API route to avoid exposing backend URLs.
21
34
  */
22
- export function useStorefrontCollections(params?: ListParams) {
23
- try {
24
- const queryParams = new URLSearchParams()
25
- if (params?.page) queryParams.set('page', params.page.toString())
26
- if (params?.pageSize) queryParams.set('pageSize', params.pageSize.toString())
27
-
28
- return useQuery({
29
- queryKey: collectionKeys.list(params),
30
- queryFn: async () => {
31
- const response = await fetch(`/api/collections?${queryParams.toString()}`)
32
- if (!response.ok) throw new Error('Failed to fetch collections')
33
- return response.json()
34
- },
35
- // medium — collections change infrequently
36
- ...getCacheTier('medium'),
37
- })
38
- } catch (error) {
39
- logger.warn('[useStorefrontCollections] Called outside QueryClientProvider, returning empty list')
40
- return {
41
- data: undefined,
42
- isLoading: false,
43
- error: null,
44
- isFetching: false,
45
- refetch: () => Promise.resolve({ data: undefined }),
46
- } as any
47
- }
35
+ export function useStorefrontCollections(params?: ListParams, config?: SdkConfig) {
36
+ const ctx = useSdkContext()
37
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
38
+ return useQuery({
39
+ queryKey: collectionKeys.list(params),
40
+ queryFn: () => getStorefrontCollections(cfg, params),
41
+ ...getCacheTier('medium'),
42
+ })
48
43
  }
49
44
 
50
45
  /**
51
46
  * Hook to fetch a single collection by slug for the storefront.
52
- * Fetches from Next.js API route to avoid exposing backend URLs.
53
47
  */
54
- export function useStorefrontCollection(slug: string) {
55
- try {
56
- return useQuery({
57
- queryKey: collectionKeys.slug(slug),
58
- queryFn: async () => {
59
- const response = await fetch(`/api/collections/${slug}`)
60
- if (!response.ok) throw new Error('Failed to fetch collection')
61
- return response.json()
62
- },
63
- // medium — collection details change infrequently
64
- ...getCacheTier('medium'),
65
- enabled: !!slug,
66
- })
67
- } catch (error) {
68
- logger.warn('[useStorefrontCollection] Called outside QueryClientProvider, returning undefined')
69
- return {
70
- data: undefined,
71
- isLoading: false,
72
- error: null,
73
- isFetching: false,
74
- refetch: () => Promise.resolve({ data: undefined }),
75
- } as any
76
- }
48
+ export function useStorefrontCollection(slug: string, config?: SdkConfig) {
49
+ const ctx = useSdkContext()
50
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
51
+ return useQuery({
52
+ queryKey: collectionKeys.slug(slug),
53
+ queryFn: () => getStorefrontCollectionBySlug(cfg, slug),
54
+ ...getCacheTier('medium'),
55
+ enabled: !!slug,
56
+ })
77
57
  }
@@ -1,145 +1,18 @@
1
1
  /**
2
- * Consultations Hooks
2
+ * Consultation Hooks
3
3
  *
4
- * React Query hooks for consultation operations.
5
- * config and accessToken are optional — if omitted, they are read from SdkContext.
4
+ * Create-only, by design — see consultation.service.ts's header comment.
6
5
  */
7
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
8
- import {
9
- getConsultations,
10
- getConsultation,
11
- createConsultation,
12
- sendMessage,
13
- acceptQuote,
14
- rejectQuote,
15
- } from '../services/consultation.service'
6
+ import { useMutation } from '@tanstack/react-query'
7
+ import { createConsultation, type CreateConsultationParams } from '../services/consultation.service'
16
8
  import type { SdkConfig } from '../config'
17
- import type {
18
- ConsultationCreatePayload,
19
- ConsultationMessagePayload,
20
- ConsultationQuoteAcceptPayload,
21
- } from '../types/consultations'
22
- import { logger } from '../utils'
23
9
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
24
10
 
25
- export function useConsultations(config?: SdkConfig, accessToken?: string | null) {
26
- const ctx = useSdkContext()
27
- try {
28
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
29
- return useQuery({
30
- queryKey: ['consultations'],
31
- queryFn: () => token ? getConsultations(cfg, token) : Promise.resolve([]),
32
- enabled: !!token,
33
- })
34
- } catch (error) {
35
- logger.warn('[useConsultations] Called outside QueryClientProvider, returning empty list')
36
- return { data: undefined, isLoading: false, error: null, isFetching: false, refetch: () => Promise.resolve({ data: undefined }) } as any
37
- }
38
- }
39
-
40
- export function useConsultation(arg1?: SdkConfig | string, arg2?: string | null, arg3?: string) {
41
- const ctx = useSdkContext()
42
- const isArg1Config = typeof arg1 === 'object' && arg1 !== null
43
- const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined
44
- const explicitToken = isArg1Config ? (arg2 as string | null) : undefined
45
- const id = isArg1Config ? arg3 : (typeof arg1 === 'string' ? arg1 : undefined)
46
-
47
- try {
48
- const { config: cfg, accessToken: token } = resolveHookArgs(explicitConfig, explicitToken, ctx)
49
- return useQuery({
50
- queryKey: ['consultation', id],
51
- queryFn: () => token && id ? getConsultation(cfg, token, id) : Promise.resolve(undefined),
52
- enabled: !!id && !!token,
53
- })
54
- } catch (error) {
55
- logger.warn('[useConsultation] Called outside QueryClientProvider, returning undefined')
56
- return { data: undefined, isLoading: false, error: null, isFetching: false, refetch: () => Promise.resolve({ data: undefined }) } as any
57
- }
58
- }
59
-
60
11
  export function useCreateConsultation(config?: SdkConfig, accessToken?: string | null) {
61
12
  const ctx = useSdkContext()
62
- try {
63
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
64
- const queryClient = useQueryClient()
65
- return useMutation({
66
- mutationFn: (payload: ConsultationCreatePayload) =>
67
- token ? createConsultation(cfg, token, payload) : Promise.reject('No access token'),
68
- onSuccess: () => {
69
- queryClient.invalidateQueries({ queryKey: ['consultations'] })
70
- },
71
- })
72
- } catch (error) {
73
- logger.warn('[useCreateConsultation] Called outside QueryClientProvider, returning no-op mutation')
74
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
75
- }
76
- }
77
-
78
- export function useSendMessage(arg1?: SdkConfig | string, arg2?: string | null, arg3?: string) {
79
- const ctx = useSdkContext()
80
- const isArg1Config = typeof arg1 === 'object' && arg1 !== null
81
- const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined
82
- const explicitToken = isArg1Config ? (arg2 as string | null) : undefined
83
- const consultationId = isArg1Config ? arg3 : (typeof arg1 === 'string' ? arg1 : undefined)
84
-
85
- try {
86
- const { config: cfg, accessToken: token } = resolveHookArgs(explicitConfig, explicitToken, ctx)
87
- const queryClient = useQueryClient()
88
- return useMutation({
89
- mutationFn: (payload: ConsultationMessagePayload) =>
90
- token && consultationId ? sendMessage(cfg, token, consultationId, payload) : Promise.reject('No access token'),
91
- onSuccess: () => {
92
- queryClient.invalidateQueries({ queryKey: ['consultation', consultationId] })
93
- },
94
- })
95
- } catch (error) {
96
- logger.warn('[useSendMessage] Called outside QueryClientProvider, returning no-op mutation')
97
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
98
- }
99
- }
100
-
101
- export function useAcceptQuote(arg1?: SdkConfig | string, arg2?: string | null, arg3?: string) {
102
- const ctx = useSdkContext()
103
- const isArg1Config = typeof arg1 === 'object' && arg1 !== null
104
- const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined
105
- const explicitToken = isArg1Config ? (arg2 as string | null) : undefined
106
- const consultationId = isArg1Config ? arg3 : (typeof arg1 === 'string' ? arg1 : undefined)
107
-
108
- try {
109
- const { config: cfg, accessToken: token } = resolveHookArgs(explicitConfig, explicitToken, ctx)
110
- const queryClient = useQueryClient()
111
- return useMutation({
112
- mutationFn: (payload: ConsultationQuoteAcceptPayload) =>
113
- token && consultationId ? acceptQuote(cfg, token, consultationId, payload) : Promise.reject('No access token'),
114
- onSuccess: () => {
115
- queryClient.invalidateQueries({ queryKey: ['consultation', consultationId] })
116
- },
117
- })
118
- } catch (error) {
119
- logger.warn('[useAcceptQuote] Called outside QueryClientProvider, returning no-op mutation')
120
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
121
- }
122
- }
123
-
124
- export function useRejectQuote(arg1?: SdkConfig | string, arg2?: string | null, arg3?: string) {
125
- const ctx = useSdkContext()
126
- const isArg1Config = typeof arg1 === 'object' && arg1 !== null
127
- const explicitConfig = isArg1Config ? (arg1 as SdkConfig) : undefined
128
- const explicitToken = isArg1Config ? (arg2 as string | null) : undefined
129
- const consultationId = isArg1Config ? arg3 : (typeof arg1 === 'string' ? arg1 : undefined)
130
-
131
- try {
132
- const { config: cfg, accessToken: token } = resolveHookArgs(explicitConfig, explicitToken, ctx)
133
- const queryClient = useQueryClient()
134
- return useMutation({
135
- mutationFn: (reason?: string) =>
136
- token && consultationId ? rejectQuote(cfg, token, consultationId, reason) : Promise.reject('No access token'),
137
- onSuccess: () => {
138
- queryClient.invalidateQueries({ queryKey: ['consultation', consultationId] })
139
- },
140
- })
141
- } catch (error) {
142
- logger.warn('[useRejectQuote] Called outside QueryClientProvider, returning no-op mutation')
143
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
144
- }
13
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
14
+ return useMutation({
15
+ mutationFn: (params: CreateConsultationParams) =>
16
+ token ? createConsultation(cfg, token, params) : Promise.reject(new Error('Please sign in to submit a request.')),
17
+ })
145
18
  }
@@ -7,40 +7,31 @@
7
7
  import { useQuery } from '@tanstack/react-query'
8
8
  import type { SdkConfig } from '../config'
9
9
  import type { Country } from '../types/ui'
10
- import { logger } from '../utils'
11
10
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
11
+ import { apiRequest, storefrontHeaders } from '../utils/http-client'
12
12
 
13
+ /**
14
+ * REAL BUG FIXED (2026-09-22): raw fetch() with a generic `HTTP ${status}`
15
+ * error — this file predates apiRequest and was missed when every other
16
+ * REST call site in the SDK was migrated onto it (it lives in a hook file,
17
+ * not services/*.ts, which is how the original migration pass missed it).
18
+ */
13
19
  export async function getCountries(config: SdkConfig): Promise<Country[]> {
14
- const response = await fetch(`${config.backendUrls.auth}/auth/countries/`, {
15
- headers: {
16
- "X-Storefront-Key": config.storefrontApiKey,
17
- "X-Business-ID": config.businessId,
18
- },
20
+ return apiRequest<Country[]>(`${config.backendUrls.auth}/auth/countries/`, {
21
+ headers: storefrontHeaders(config),
22
+ fallbackErrorMessage: 'Failed to fetch countries',
19
23
  })
20
- if (!response.ok) throw new Error('Failed to fetch countries')
21
- return response.json()
22
24
  }
23
25
 
24
26
  export function useCountries(config?: SdkConfig) {
25
27
  const ctx = useSdkContext()
26
- try {
27
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
28
- return useQuery({
29
- queryKey: ['countries'],
30
- queryFn: () => getCountries(cfg),
31
- retry: 1,
32
- refetchOnWindowFocus: false,
33
- refetchOnReconnect: false,
34
- staleTime: 24 * 60 * 60 * 1000,
35
- })
36
- } catch (error) {
37
- logger.warn('[useCountries] Called outside QueryClientProvider, returning empty list')
38
- return {
39
- data: [],
40
- isLoading: false,
41
- error: null,
42
- isFetching: false,
43
- refetch: () => Promise.resolve({ data: [] }),
44
- } as any
45
- }
28
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
29
+ return useQuery({
30
+ queryKey: ['countries'],
31
+ queryFn: () => getCountries(cfg),
32
+ retry: 1,
33
+ refetchOnWindowFocus: false,
34
+ refetchOnReconnect: false,
35
+ staleTime: 24 * 60 * 60 * 1000,
36
+ })
46
37
  }
@@ -9,32 +9,20 @@ import { getCacheTier } from '../utils/cache'
9
9
  import { getSupportedCurrencies } from '../services/config.service'
10
10
  import type { SdkConfig } from '../config'
11
11
  import type { Currency } from '../types/payments'
12
- import { logger } from '../utils'
13
12
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
14
13
 
15
14
  export async function getSupportedCurrenciesList(config: SdkConfig): Promise<Currency[]> {
16
15
  const data = await getSupportedCurrencies(config)
17
- return data.currencies || []
16
+ return (data.currencies || []).filter((currency: Currency) => currency.code !== 'auto')
18
17
  }
19
18
 
20
19
  export function useCurrencies(config?: SdkConfig) {
21
20
  const ctx = useSdkContext()
22
- try {
23
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
24
- return useQuery({
25
- queryKey: ['supported-currencies'],
26
- queryFn: () => getSupportedCurrenciesList(cfg),
27
- retry: 1,
28
- ...getCacheTier('high'),
29
- })
30
- } catch (error) {
31
- logger.warn('[useCurrencies] Called outside QueryClientProvider, returning empty list')
32
- return {
33
- data: [],
34
- isLoading: false,
35
- error: null,
36
- isFetching: false,
37
- refetch: () => Promise.resolve({ data: [] }),
38
- } as any
39
- }
21
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
22
+ return useQuery({
23
+ queryKey: ['supported-currencies', cfg.businessId],
24
+ queryFn: () => getSupportedCurrenciesList(cfg),
25
+ retry: 1,
26
+ ...getCacheTier('high'),
27
+ })
40
28
  }