@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.
- package/dist/hooks/index.d.mts +429 -0
- package/dist/hooks/index.d.ts +429 -0
- package/dist/hooks/index.js +3073 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2962 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +250 -1161
- package/dist/index.d.ts +250 -1161
- package/dist/index.js +1544 -2359
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1527 -2258
- package/dist/index.mjs.map +1 -1
- package/dist/sdk-context-Cy51kEKT.d.mts +565 -0
- package/dist/sdk-context-Cy51kEKT.d.ts +565 -0
- package/package.json +6 -2
- package/src/config/index.ts +10 -91
- package/src/constants.ts +13 -0
- package/src/context/sdk-context.tsx +70 -0
- package/src/graphql/collections.graphql.ts +2 -10
- package/src/graphql/products.graphql.ts +215 -65
- package/src/graphql/wishlist.graphql.ts +7 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-auth.ts +82 -158
- package/src/hooks/use-bespoke-availability.ts +25 -8
- package/src/hooks/use-cart.ts +222 -119
- package/src/hooks/use-cms.ts +179 -86
- package/src/hooks/use-collections.ts +52 -15
- package/src/hooks/use-consultation-stream.ts +4 -3
- package/src/hooks/use-consultations.ts +120 -51
- package/src/hooks/use-countries.ts +35 -13
- package/src/hooks/use-currencies.ts +26 -13
- package/src/hooks/use-google-translate.ts +22 -8
- package/src/hooks/use-languages.ts +40 -20
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-order-stream.ts +9 -5
- package/src/hooks/use-orders.ts +99 -44
- package/src/hooks/use-payment.ts +108 -40
- package/src/hooks/use-products.ts +251 -53
- package/src/hooks/use-service-booking.ts +152 -90
- package/src/hooks/use-states.ts +35 -15
- package/src/hooks/use-wishlist.ts +92 -91
- package/src/index.ts +26 -30
- package/src/services/auth.service.ts +209 -167
- package/src/services/bespoke.service.ts +12 -4
- package/src/services/cart.service.ts +172 -70
- package/src/services/cms.service.ts +70 -35
- package/src/services/collection.service.ts +22 -5
- package/src/services/config.service.ts +34 -14
- package/src/services/consultation.service.ts +72 -18
- package/src/services/logistics.service.ts +35 -11
- package/src/services/notifications.service.ts +27 -12
- package/src/services/order.service.ts +74 -15
- package/src/services/payment.service.ts +86 -31
- package/src/services/product.service.ts +279 -39
- package/src/services/service-booking.service.ts +98 -26
- package/src/services/wishlist.service.ts +69 -25
- package/src/types/auth/index.ts +6 -4
- package/src/types/business/index.ts +7 -2
- package/src/types/payments/index.ts +3 -3
- package/src/types/services/index.ts +2 -5
- package/src/types/store/index.ts +38 -3
- package/src/utils/error-handler.ts +10 -5
- package/src/utils/storage.ts +48 -26
- package/src/utils/validators.ts +85 -0
- package/src/client/authClient.ts +0 -135
- package/src/client/graphqlClient.ts +0 -53
- package/src/client/httpClient.ts +0 -298
package/src/hooks/use-cart.ts
CHANGED
|
@@ -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
|
|
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
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
85
|
+
const isOpen = context ? context.isOpen : false
|
|
86
|
+
const setOpen = context ? context.setOpen : () => {}
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
}
|