@kasuvia/sdk 0.0.2 → 0.0.4

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 (67) hide show
  1. package/dist/hooks/index.d.mts +429 -0
  2. package/dist/hooks/index.d.ts +429 -0
  3. package/dist/hooks/index.js +3073 -0
  4. package/dist/hooks/index.js.map +1 -0
  5. package/dist/hooks/index.mjs +2962 -0
  6. package/dist/hooks/index.mjs.map +1 -0
  7. package/dist/index.d.mts +250 -1161
  8. package/dist/index.d.ts +250 -1161
  9. package/dist/index.js +1544 -2359
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1527 -2258
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/sdk-context-Cy51kEKT.d.mts +565 -0
  14. package/dist/sdk-context-Cy51kEKT.d.ts +565 -0
  15. package/package.json +6 -2
  16. package/src/config/index.ts +10 -91
  17. package/src/constants.ts +13 -0
  18. package/src/context/sdk-context.tsx +70 -0
  19. package/src/graphql/collections.graphql.ts +2 -10
  20. package/src/graphql/products.graphql.ts +215 -65
  21. package/src/graphql/wishlist.graphql.ts +7 -6
  22. package/src/hooks/index.ts +2 -0
  23. package/src/hooks/use-auth.ts +82 -158
  24. package/src/hooks/use-bespoke-availability.ts +25 -8
  25. package/src/hooks/use-cart.ts +222 -119
  26. package/src/hooks/use-cms.ts +179 -86
  27. package/src/hooks/use-collections.ts +52 -15
  28. package/src/hooks/use-consultation-stream.ts +4 -3
  29. package/src/hooks/use-consultations.ts +120 -51
  30. package/src/hooks/use-countries.ts +35 -13
  31. package/src/hooks/use-currencies.ts +26 -13
  32. package/src/hooks/use-google-translate.ts +22 -8
  33. package/src/hooks/use-languages.ts +40 -20
  34. package/src/hooks/use-mobile.ts +23 -0
  35. package/src/hooks/use-order-stream.ts +9 -5
  36. package/src/hooks/use-orders.ts +99 -44
  37. package/src/hooks/use-payment.ts +108 -40
  38. package/src/hooks/use-products.ts +251 -53
  39. package/src/hooks/use-service-booking.ts +152 -90
  40. package/src/hooks/use-states.ts +35 -15
  41. package/src/hooks/use-wishlist.ts +92 -91
  42. package/src/index.ts +26 -30
  43. package/src/services/auth.service.ts +209 -167
  44. package/src/services/bespoke.service.ts +12 -4
  45. package/src/services/cart.service.ts +172 -70
  46. package/src/services/cms.service.ts +70 -35
  47. package/src/services/collection.service.ts +22 -5
  48. package/src/services/config.service.ts +34 -14
  49. package/src/services/consultation.service.ts +72 -18
  50. package/src/services/logistics.service.ts +35 -11
  51. package/src/services/notifications.service.ts +27 -12
  52. package/src/services/order.service.ts +74 -15
  53. package/src/services/payment.service.ts +86 -31
  54. package/src/services/product.service.ts +279 -39
  55. package/src/services/service-booking.service.ts +98 -26
  56. package/src/services/wishlist.service.ts +69 -25
  57. package/src/types/auth/index.ts +6 -4
  58. package/src/types/business/index.ts +7 -2
  59. package/src/types/payments/index.ts +3 -3
  60. package/src/types/services/index.ts +2 -5
  61. package/src/types/store/index.ts +38 -3
  62. package/src/utils/error-handler.ts +10 -5
  63. package/src/utils/storage.ts +48 -26
  64. package/src/utils/validators.ts +85 -0
  65. package/src/client/authClient.ts +0 -135
  66. package/src/client/graphqlClient.ts +0 -53
  67. package/src/client/httpClient.ts +0 -298
@@ -2,13 +2,14 @@
2
2
  * Cart Hook & Context Provider
3
3
  *
4
4
  * React Query hooks for cart operations with unified drawer state sharing.
5
+ * config and accessToken are optional — if omitted, they are read from SdkContext.
5
6
  */
6
7
  import React, { createContext, useContext, useState, type ReactNode } from 'react'
7
8
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
8
9
  import { getCart, addToCart, updateCartItem, removeFromCart, clearCart, mergeGuestCartIfPresent } from '../services/cart.service'
9
- import { isInitialized } from '../config'
10
10
  import type { CartItem, CartResponse, Product } from '../types/store'
11
- import axios from 'axios'
11
+ import type { SdkConfig } from '../config'
12
+ import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
12
13
 
13
14
  interface ProductTypeMismatchDetail {
14
15
  code?: string
@@ -41,7 +42,7 @@ export interface UseCartReturn {
41
42
  removeFromCart: (itemId: string) => void
42
43
  clearCart: () => void
43
44
  clear: () => void
44
- mergeGuestCart: () => void
45
+ mergeGuestCart: (guestSessionId: string) => void
45
46
  addItem: (product: Product, variantId?: string, quantity?: number, customMeasurements?: Record<string, string>) => void
46
47
  updateQty: (itemId: string, quantity: number) => void
47
48
  removeItem: (itemId: string) => void
@@ -72,136 +73,238 @@ export function CartProvider({ children }: { children: ReactNode }) {
72
73
 
73
74
  // ── Hook ──────────────────────────────────────────────────────────────────────
74
75
 
75
- export function useCart(): UseCartReturn {
76
- const queryClient = useQueryClient()
77
- const context = CartCtx ? useContext(CartCtx) : null
76
+ export function useCart(config?: SdkConfig, accessToken?: string | null): UseCartReturn {
77
+ const ctx = useSdkContext();
78
+ try {
79
+ const resolved = resolveHookArgs(config, accessToken, ctx);
80
+ const resolvedConfig = resolved.config;
81
+ const resolvedToken = resolved.accessToken;
82
+ const queryClient = useQueryClient()
83
+ const context = CartCtx ? useContext(CartCtx) : null
78
84
 
79
- const isOpen = context ? context.isOpen : false
80
- const setOpen = context ? context.setOpen : () => {}
85
+ const isOpen = context ? context.isOpen : false
86
+ const setOpen = context ? context.setOpen : () => {}
81
87
 
82
- // Track product type mismatch errors from cart operations
83
- const [productTypeMismatch, setProductTypeMismatch] = useState<{
84
- message: string
85
- existing_type: string
86
- new_type: string
87
- } | null>(null)
88
-
89
- const cartQuery = useQuery({
90
- queryKey: ['cart'],
91
- queryFn: getCart,
92
- enabled: isInitialized(),
93
- })
94
-
95
- const addToCartMutation = useMutation({
96
- mutationFn: addToCart,
97
- onSuccess: () => {
98
- setProductTypeMismatch(null) // Clear error on success
99
- queryClient.invalidateQueries({ queryKey: ['cart'] })
100
- },
101
- onError: (error: unknown) => {
102
- // Handle product type mismatch errors from backend
103
- if (axios.isAxiosError(error) && error.response?.status === 409) {
104
- const responseData = error.response?.data as { detail?: ProductTypeMismatchDetail } | undefined
105
- const errorDetail = responseData?.detail
106
- if (errorDetail?.code === 'CART_PRODUCT_TYPE_MISMATCH') {
88
+ // Track product type mismatch errors from cart operations
89
+ const [productTypeMismatch, setProductTypeMismatch] = useState<{
90
+ message: string
91
+ existing_type: string
92
+ new_type: string
93
+ } | null>(null)
94
+
95
+ const cartQuery = useQuery({
96
+ queryKey: ['cart'],
97
+ queryFn: async () => {
98
+ if (resolvedToken) {
99
+ return getCart(resolvedConfig, resolvedToken)
100
+ }
101
+ if (typeof window !== 'undefined') {
102
+ const res = await fetch('/api/cart', { credentials: 'include' })
103
+ if (!res.ok) throw new Error('Failed to fetch cart')
104
+ return res.json()
105
+ }
106
+ return undefined
107
+ },
108
+ enabled: true,
109
+ })
110
+
111
+ const addToCartMutation = useMutation({
112
+ mutationFn: async (item: Parameters<typeof addToCart>[2]) => {
113
+ if (resolvedToken) {
114
+ return addToCart(resolvedConfig, resolvedToken, item)
115
+ }
116
+ if (typeof window !== 'undefined') {
117
+ const res = await fetch('/api/cart', {
118
+ method: 'POST',
119
+ headers: { 'Content-Type': 'application/json' },
120
+ credentials: 'include',
121
+ body: JSON.stringify(item),
122
+ })
123
+ if (!res.ok) {
124
+ const data = await res.json().catch(() => ({}))
125
+ throw { status: res.status, data }
126
+ }
127
+ return res.json()
128
+ }
129
+ throw new Error('No access token and not in browser context')
130
+ },
131
+ onSuccess: () => {
132
+ setProductTypeMismatch(null)
133
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
134
+ },
135
+ onError: (error: unknown) => {
136
+ // Handle product type mismatch errors from backend
137
+ const err = error as { status?: number; data?: { detail?: ProductTypeMismatchDetail } }
138
+ if (err.status === 409 && err.data?.detail?.code === 'CART_PRODUCT_TYPE_MISMATCH') {
139
+ const errorDetail = err.data.detail
107
140
  setProductTypeMismatch({
108
141
  message: errorDetail.message ?? 'Your cart contains incompatible items.',
109
142
  existing_type: errorDetail.existing_type ?? 'physical',
110
143
  new_type: errorDetail.new_type ?? 'physical',
111
144
  })
112
145
  }
113
- }
114
- },
115
- })
116
-
117
- const updateCartItemMutation = useMutation({
118
- mutationFn: ({ itemId, quantity }: { itemId: string; quantity: number }) =>
119
- updateCartItem(itemId, quantity),
120
- onSuccess: () => {
121
- queryClient.invalidateQueries({ queryKey: ['cart'] })
122
- },
123
- })
124
-
125
- const removeFromCartMutation = useMutation({
126
- mutationFn: removeFromCart,
127
- onSuccess: () => {
128
- queryClient.invalidateQueries({ queryKey: ['cart'] })
129
- },
130
- })
131
-
132
- const clearCartMutation = useMutation({
133
- mutationFn: clearCart,
134
- onSuccess: () => {
135
- queryClient.invalidateQueries({ queryKey: ['cart'] })
136
- },
137
- })
138
-
139
- const mergeGuestCartMutation = useMutation({
140
- mutationFn: mergeGuestCartIfPresent,
141
- onSuccess: () => {
142
- queryClient.invalidateQueries({ queryKey: ['cart'] })
143
- },
144
- })
145
-
146
- // Convenience method for adding items with product object
147
- const addItem = (product: Product, variantId?: string, quantity: number = 1, customMeasurements?: Record<string, string>) => {
148
- const variant = variantId ? product.variants.find(v => v.id === variantId) : undefined
149
- return addToCartMutation.mutate({
150
- product_id: product.id,
151
- product_slug: product.slug,
152
- product_name: product.name,
153
- image: product.images[0]?.url,
154
- variant_id: variantId,
155
- variant_attributes: variant?.attributes ? JSON.stringify(variant.attributes) : undefined,
156
- unit_price: variant?.price ?? product.base_price,
157
- quantity,
158
- custom_measurements: customMeasurements ? JSON.stringify(customMeasurements) : undefined,
159
- is_bespoke: product.is_bespoke,
146
+ },
160
147
  })
161
- }
162
148
 
163
- // Helper methods for storefront compatibility
164
- const items = cartQuery.data?.items || []
165
- const subtotal = cartQuery.data?.subtotal || 0
166
- const count = items.reduce((total, item) => total + item.quantity, 0)
149
+ const updateCartItemMutation = useMutation({
150
+ mutationFn: async ({ itemId, quantity }: { itemId: string; quantity: number }) => {
151
+ if (resolvedToken) {
152
+ return updateCartItem(resolvedConfig, resolvedToken, itemId, quantity)
153
+ }
154
+ if (typeof window !== 'undefined') {
155
+ const res = await fetch(`/api/cart/${itemId}`, {
156
+ method: 'PATCH',
157
+ headers: { 'Content-Type': 'application/json' },
158
+ credentials: 'include',
159
+ body: JSON.stringify({ quantity }),
160
+ })
161
+ if (!res.ok) throw new Error('Failed to update cart item')
162
+ return res.json()
163
+ }
164
+ throw new Error('No access token')
165
+ },
166
+ onSuccess: () => {
167
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
168
+ },
169
+ })
167
170
 
168
- const updateQty = (itemId: string, quantity: number) => {
169
- updateCartItemMutation.mutate({ itemId, quantity })
170
- }
171
+ const removeFromCartMutation = useMutation({
172
+ mutationFn: async (itemId: string) => {
173
+ if (resolvedToken) {
174
+ return removeFromCart(resolvedConfig, resolvedToken, itemId)
175
+ }
176
+ if (typeof window !== 'undefined') {
177
+ const res = await fetch(`/api/cart/${itemId}`, {
178
+ method: 'DELETE',
179
+ credentials: 'include',
180
+ })
181
+ if (!res.ok) throw new Error('Failed to remove cart item')
182
+ return res.json()
183
+ }
184
+ throw new Error('No access token')
185
+ },
186
+ onSuccess: () => {
187
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
188
+ },
189
+ })
171
190
 
172
- const removeItem = (itemId: string) => {
173
- removeFromCartMutation.mutate(itemId)
174
- }
191
+ const clearCartMutation = useMutation({
192
+ mutationFn: async () => {
193
+ if (resolvedToken) {
194
+ return clearCart(resolvedConfig, resolvedToken)
195
+ }
196
+ if (typeof window !== 'undefined') {
197
+ const res = await fetch('/api/cart/clear', {
198
+ method: 'DELETE',
199
+ credentials: 'include',
200
+ })
201
+ if (!res.ok) throw new Error('Failed to clear cart')
202
+ return res.json()
203
+ }
204
+ throw new Error('No access token')
205
+ },
206
+ onSuccess: () => {
207
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
208
+ },
209
+ })
175
210
 
176
- const handleClearCartAndAdd = () => {
177
- clearCartMutation.mutate()
178
- setProductTypeMismatch(null)
179
- }
211
+ const mergeGuestCartMutation = useMutation({
212
+ mutationFn: (guestSessionId: string) =>
213
+ resolvedToken ? mergeGuestCartIfPresent(resolvedConfig, resolvedToken, guestSessionId) : Promise.resolve(),
214
+ onSuccess: () => {
215
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
216
+ },
217
+ })
180
218
 
181
- const handleKeepCurrentCart = () => {
182
- setProductTypeMismatch(null)
183
- }
219
+ // Convenience method for adding items with product object
220
+ const addItem = (product: Product, variantId?: string, quantity: number = 1, customMeasurements?: Record<string, string>) => {
221
+ const variant = variantId ? product.variants.find(v => v.id === variantId) : undefined
222
+ return addToCartMutation.mutate({
223
+ product_id: product.id,
224
+ product_slug: product.slug,
225
+ product_name: product.name,
226
+ image: product.images[0] || undefined,
227
+ variant_id: variantId,
228
+ variant_attributes: variant?.attributes ? JSON.stringify(variant.attributes) : undefined,
229
+ unit_price: variant?.price ?? product.base_price,
230
+ quantity,
231
+ custom_measurements: customMeasurements ? JSON.stringify(customMeasurements) : undefined,
232
+ is_bespoke: product.is_bespoke,
233
+ })
234
+ }
235
+
236
+ // Helper methods for storefront compatibility
237
+ const items = cartQuery.data?.items || []
238
+ const subtotal = cartQuery.data?.subtotal || 0
239
+ const count = items.reduce((total: number, item: CartItem) => total + item.quantity, 0)
240
+
241
+ const updateQty = (itemId: string, quantity: number) => {
242
+ updateCartItemMutation.mutate({ itemId, quantity })
243
+ }
244
+
245
+ const removeItem = (itemId: string) => {
246
+ removeFromCartMutation.mutate(itemId)
247
+ }
248
+
249
+ const handleClearCartAndAdd = () => {
250
+ clearCartMutation.mutate()
251
+ setProductTypeMismatch(null)
252
+ }
253
+
254
+ const handleKeepCurrentCart = () => {
255
+ setProductTypeMismatch(null)
256
+ }
184
257
 
185
- return {
186
- cart: cartQuery.data,
187
- items,
188
- count,
189
- isOpen,
190
- setOpen,
191
- isLoading: cartQuery.isLoading,
192
- error: cartQuery.error,
193
- addToCart: addToCartMutation.mutate,
194
- updateCartItem: updateCartItemMutation.mutate,
195
- removeFromCart: removeFromCartMutation.mutate,
196
- clearCart: clearCartMutation.mutate,
197
- clear: clearCartMutation.mutate, // Alias for storefront compatibility
198
- mergeGuestCart: mergeGuestCartMutation.mutate,
199
- addItem,
200
- updateQty,
201
- removeItem,
202
- subtotal,
203
- productTypeMismatch,
204
- handleClearCartAndAdd,
205
- handleKeepCurrentCart,
258
+ return {
259
+ cart: cartQuery.data,
260
+ items,
261
+ count,
262
+ isOpen,
263
+ setOpen,
264
+ isLoading: cartQuery.isLoading,
265
+ error: cartQuery.error,
266
+ addToCart: addToCartMutation.mutate,
267
+ updateCartItem: updateCartItemMutation.mutate,
268
+ removeFromCart: removeFromCartMutation.mutate,
269
+ clearCart: clearCartMutation.mutate,
270
+ clear: clearCartMutation.mutate, // Alias for storefront compatibility
271
+ mergeGuestCart: mergeGuestCartMutation.mutate,
272
+ addItem,
273
+ updateQty,
274
+ removeItem,
275
+ subtotal,
276
+ productTypeMismatch,
277
+ handleClearCartAndAdd,
278
+ handleKeepCurrentCart,
279
+ }
280
+ } catch (error) {
281
+ // If called outside QueryClientProvider, return safe fallback
282
+ console.warn('[useCart] Called outside QueryClientProvider, returning empty cart')
283
+ const context = CartCtx ? useContext(CartCtx) : null
284
+ const isOpen = context ? context.isOpen : false
285
+ const setOpen = context ? context.setOpen : () => {}
286
+
287
+ return {
288
+ cart: undefined,
289
+ items: [],
290
+ count: 0,
291
+ isOpen,
292
+ setOpen,
293
+ isLoading: false,
294
+ error: null,
295
+ addToCart: () => {},
296
+ updateCartItem: () => {},
297
+ removeFromCart: () => {},
298
+ clearCart: () => {},
299
+ clear: () => {},
300
+ mergeGuestCart: () => {},
301
+ addItem: () => {},
302
+ updateQty: () => {},
303
+ removeItem: () => {},
304
+ subtotal: 0,
305
+ productTypeMismatch: null,
306
+ handleClearCartAndAdd: () => {},
307
+ handleKeepCurrentCart: () => {},
308
+ } as any
206
309
  }
207
310
  }