@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.
- package/README.md +42 -15
- package/dist/AddressAutocomplete-CjcqPElD.d.mts +46 -0
- package/dist/AddressAutocomplete-CvJHuRkf.d.ts +46 -0
- package/dist/components/index.d.mts +497 -0
- package/dist/components/index.d.ts +497 -0
- package/dist/components/index.js +4217 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +4194 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/server.d.mts +26 -0
- package/dist/config/server.d.ts +26 -0
- package/dist/config/server.js +38 -0
- package/dist/config/server.js.map +1 -0
- package/dist/config/server.mjs +13 -0
- package/dist/config/server.mjs.map +1 -0
- package/dist/hooks/index.d.mts +393 -160
- package/dist/hooks/index.d.ts +393 -160
- package/dist/hooks/index.js +2792 -2137
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +2734 -2121
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index-CNW_LINy.d.mts +578 -0
- package/dist/index-CNW_LINy.d.ts +578 -0
- package/dist/{index-C1chUA3P.d.mts → index-F7N6UItS.d.mts} +1 -1
- package/dist/{index-C1chUA3P.d.ts → index-F7N6UItS.d.ts} +1 -1
- package/dist/index-NkI10J_E.d.mts +40 -0
- package/dist/index-NkI10J_E.d.ts +40 -0
- package/dist/index.d.mts +20437 -103
- package/dist/index.d.ts +20437 -103
- package/dist/index.js +2111 -1802
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2006 -1786
- package/dist/index.mjs.map +1 -1
- package/dist/media.service-Cx7dQjzX.d.mts +1393 -0
- package/dist/media.service-mrBFJ2UQ.d.ts +1393 -0
- package/dist/services/index.d.mts +908 -2
- package/dist/services/index.d.ts +908 -2
- package/dist/services/index.js +1492 -1686
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +1431 -1670
- package/dist/services/index.mjs.map +1 -1
- package/package.json +46 -6
- package/src/components/bespoke/BespokeAvailabilityBadge.tsx +32 -0
- package/src/components/bespoke/BespokeProgressTracker.tsx +58 -0
- package/src/components/bespoke/index.ts +3 -0
- package/src/components/booking/SlotPicker.tsx +97 -0
- package/src/components/booking/index.ts +1 -0
- package/src/components/cart/CartItem.tsx +81 -0
- package/src/components/cart/CartSidebar.tsx +115 -0
- package/src/components/cart/CartTypeMismatchBanner.tsx +55 -0
- package/src/components/cart/index.ts +3 -0
- package/src/components/index.ts +8 -0
- package/src/components/orders/DeliverableReviewer.tsx +139 -0
- package/src/components/orders/index.ts +1 -0
- package/src/components/product/ProductCard.tsx +350 -0
- package/src/components/product/ProductGrid.tsx +84 -0
- package/src/components/product/index.ts +2 -0
- package/src/components/shared/AddressAutocomplete.tsx +212 -0
- package/src/components/shared/AnalyticsScripts.tsx +56 -0
- package/src/components/shared/CookieConsentBanner.tsx +98 -0
- package/src/components/shared/ImageEditorModal.tsx +1066 -0
- package/src/components/shared/ImageViewerModal.tsx +598 -0
- package/src/components/shared/LucideReactIcon.tsx +52 -0
- package/src/components/shared/MapPinDrop.tsx +101 -0
- package/src/components/shared/MultiSelectPicker.tsx +193 -0
- package/src/components/shared/OfflineBanner.tsx +46 -0
- package/src/components/shared/SearchableInput.tsx +153 -0
- package/src/components/shared/SearchablePopover.tsx +166 -0
- package/src/components/shared/SmartImage.tsx +311 -0
- package/src/components/shared/TruncatedValue.tsx +27 -0
- package/src/components/shared/UnsavedChangesGuard.tsx +138 -0
- package/src/components/shared/index.ts +15 -0
- package/src/components/skeletons/CardSkeleton.tsx +26 -0
- package/src/components/skeletons/CartItemSkeleton.tsx +26 -0
- package/src/components/skeletons/OrderItemSkeleton.tsx +26 -0
- package/src/components/skeletons/ProductCardSkeleton.tsx +26 -0
- package/src/components/skeletons/TableSkeleton.tsx +42 -0
- package/src/components/skeletons/index.ts +5 -0
- package/src/config/index.ts +22 -4
- package/src/config/server.ts +45 -0
- package/src/context/sdk-context.tsx +5 -2
- package/src/generated/auth-openapi.ts +2935 -0
- package/src/generated/cart-openapi.ts +652 -0
- package/src/generated/cms-openapi.ts +1668 -0
- package/src/generated/crm-openapi.ts +986 -0
- package/src/generated/graphql.ts +126 -0
- package/src/generated/index.ts +11 -0
- package/src/generated/media-openapi.ts +613 -0
- package/src/generated/notification-openapi.ts +862 -0
- package/src/generated/payment-openapi.ts +2713 -0
- package/src/generated/store-openapi.ts +9510 -0
- package/src/graphql/operations/collections.graphql +32 -0
- package/src/graphql/operations/products.graphql +197 -0
- package/src/graphql/operations/wishlist.graphql +25 -0
- package/src/hooks/index.ts +9 -2
- package/src/hooks/use-auth.ts +163 -69
- package/src/hooks/use-bespoke-availability.ts +39 -22
- package/src/hooks/use-business-config.ts +51 -0
- package/src/hooks/use-cart.ts +310 -238
- package/src/hooks/use-cms.ts +107 -127
- package/src/hooks/use-collections.ts +35 -55
- package/src/hooks/use-consultations.ts +9 -136
- package/src/hooks/use-countries.ts +19 -28
- package/src/hooks/use-currencies.ts +8 -20
- package/src/hooks/use-customer-address.ts +64 -0
- package/src/hooks/use-feedback.ts +34 -0
- package/src/hooks/use-languages.ts +26 -39
- package/src/hooks/use-media-upload.ts +23 -0
- package/src/hooks/use-notification-stream.ts +88 -0
- package/src/hooks/use-notifications.ts +163 -0
- package/src/hooks/use-order-stream.ts +29 -29
- package/src/hooks/use-orders.ts +91 -73
- package/src/hooks/use-payment.ts +116 -84
- package/src/hooks/use-products.ts +65 -269
- package/src/hooks/use-push-notifications.ts +122 -0
- package/src/hooks/use-reviews.ts +175 -0
- package/src/hooks/use-service-booking.ts +109 -132
- package/src/hooks/use-states.ts +16 -30
- package/src/hooks/use-wishlist.ts +44 -69
- package/src/index.ts +15 -11
- package/src/services/auth.service.ts +422 -132
- package/src/services/bespoke.service.ts +69 -10
- package/src/services/cart.service.ts +155 -186
- package/src/services/cms.service.ts +41 -50
- package/src/services/collection.service.ts +25 -63
- package/src/services/config.service.ts +10 -21
- package/src/services/consultation.service.ts +36 -93
- package/src/services/feedback.service.ts +76 -0
- package/src/services/index.ts +7 -1
- package/src/services/legal.service.ts +62 -0
- package/src/services/logistics.service.ts +235 -53
- package/src/services/media.service.ts +115 -0
- package/src/services/notifications.service.ts +204 -20
- package/src/services/order.service.ts +498 -127
- package/src/services/payment.service.ts +149 -100
- package/src/services/product.service.ts +157 -814
- package/src/services/review.service.ts +211 -0
- package/src/services/service-booking.service.ts +198 -99
- package/src/services/wishlist.service.ts +27 -74
- package/src/types/auth/index.ts +161 -18
- package/src/types/business/index.ts +9 -1
- package/src/types/cms/index.ts +91 -6
- package/src/types/cms/legal.ts +18 -0
- package/src/types/consultations/index.ts +60 -68
- package/src/types/index.ts +5 -3
- package/src/types/media/index.ts +35 -3
- package/src/types/payments/index.ts +80 -16
- package/src/types/reviews/index.ts +116 -0
- package/src/types/services/index.ts +113 -38
- package/src/types/store/checkout.ts +53 -0
- package/src/types/store/index.ts +273 -52
- package/src/types/store/invoice.ts +31 -0
- package/src/types/store/shipping.ts +84 -3
- package/src/types/ui/index.ts +1 -1
- package/src/utils/checkout.ts +19 -3
- package/src/utils/cookieConsent.ts +34 -0
- package/src/utils/format.ts +408 -0
- package/src/utils/geolocation.ts +134 -0
- package/src/utils/graphql-url.ts +47 -0
- package/src/utils/guest-session.ts +59 -0
- package/src/utils/http-client.ts +147 -0
- package/src/utils/index.ts +11 -4
- package/src/utils/status-colors.ts +79 -0
- package/src/utils/token-refresh.ts +115 -0
- package/src/utils/validators.ts +13 -0
- package/src/utils/youtube.ts +18 -0
- package/dist/index-C5YO9gTl.d.mts +0 -507
- package/dist/index-C5YO9gTl.d.ts +0 -507
- package/dist/index-CefG7_xq.d.mts +0 -793
- package/dist/index-DeHCf3zI.d.ts +0 -793
- package/src/graphql/collections.graphql.ts +0 -48
- package/src/graphql/index.ts +0 -7
- package/src/graphql/products.graphql.ts +0 -860
- package/src/graphql/wishlist.graphql.ts +0 -34
- package/src/hooks/use-consultation-stream.ts +0 -142
- package/src/types/bespoke/index.ts +0 -39
package/src/hooks/use-cms.ts
CHANGED
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
5
|
-
*
|
|
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 {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
*
|
|
2
|
+
* Consultation Hooks
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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 {
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
15
|
-
headers:
|
|
16
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
}
|