@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
@@ -0,0 +1,32 @@
1
+ fragment CollectionFields on Collection {
2
+ id
3
+ business_id
4
+ name
5
+ slug
6
+ description
7
+ image_url
8
+ collection_type
9
+ rules
10
+ status
11
+ sort_order
12
+ created_at
13
+ updated_at
14
+ }
15
+
16
+ query GetStorefrontCollections($page: Int!, $pageSize: Int!) {
17
+ storefront_collections(page: $page, pageSize: $pageSize) {
18
+ items {
19
+ ...CollectionFields
20
+ }
21
+ total
22
+ page
23
+ page_size
24
+ total_pages
25
+ }
26
+ }
27
+
28
+ query GetStorefrontCollectionBySlug($slug: String!) {
29
+ storefront_collection_by_slug(slug: $slug) {
30
+ ...CollectionFields
31
+ }
32
+ }
@@ -0,0 +1,197 @@
1
+ # Shared field selection for every storefront product query. Replaces 7
2
+ # hand-duplicated copies of this exact selection that used to live inline in
3
+ # product.service.ts (see that file's normaliseProductType doc comment for
4
+ # why keeping them in sync by hand was the root cause of a platform-wide bug).
5
+ fragment ProductFields on Product {
6
+ id
7
+ business_id
8
+ name
9
+ slug
10
+ description
11
+ brand
12
+ product_type
13
+ requires_shipping
14
+ is_consumable
15
+ is_bespoke
16
+ is_physical
17
+ is_non_returnable
18
+ deposit_percentage
19
+ base_price
20
+ currency
21
+ sale_price
22
+ sale_start
23
+ sale_end
24
+ images
25
+ tags
26
+ custom_fields {
27
+ label
28
+ unit
29
+ required
30
+ field_type
31
+ options
32
+ }
33
+ is_published
34
+ is_featured
35
+ is_popular
36
+ is_new
37
+ is_temporary
38
+ average_rating
39
+ rating_count
40
+ source_channel
41
+ source_channel_ref_id
42
+ service_type
43
+ service_duration_minutes
44
+ service_location_type
45
+ service_coverage_radius_km
46
+ service_requires_consultation
47
+ service_max_capacity_per_slot
48
+ service_buffer_minutes
49
+ service_is_recurring
50
+ service_recurring_interval
51
+ service_project_based
52
+ digital_files {
53
+ id
54
+ file_key
55
+ display_name
56
+ sort_order
57
+ }
58
+ download_limit
59
+ download_expires_at
60
+ weight_kg
61
+ length_cm
62
+ width_cm
63
+ height_cm
64
+ created_at
65
+ updated_at
66
+ variants {
67
+ id
68
+ product_id
69
+ sku
70
+ attributes
71
+ price
72
+ images
73
+ weight_kg
74
+ is_active
75
+ inventory {
76
+ id
77
+ product_id
78
+ variant_id
79
+ quantity
80
+ reserved
81
+ reorder_point
82
+ reorder_quantity
83
+ track_quantity
84
+ allow_backorder
85
+ available
86
+ is_in_stock
87
+ is_low_stock
88
+ updated_at
89
+ }
90
+ created_at
91
+ updated_at
92
+ }
93
+ collections {
94
+ id
95
+ business_id
96
+ name
97
+ slug
98
+ description
99
+ image_url
100
+ collection_type
101
+ rules
102
+ status
103
+ sort_order
104
+ created_at
105
+ updated_at
106
+ }
107
+ }
108
+
109
+ query GetStorefrontProducts(
110
+ $page: Int!
111
+ $pageSize: Int!
112
+ $collectionSlug: String
113
+ $search: String
114
+ $productType: String
115
+ ) {
116
+ storefront_products(
117
+ page: $page
118
+ pageSize: $pageSize
119
+ collectionSlug: $collectionSlug
120
+ search: $search
121
+ productType: $productType
122
+ ) {
123
+ items {
124
+ ...ProductFields
125
+ }
126
+ total
127
+ page
128
+ page_size
129
+ total_pages
130
+ }
131
+ }
132
+
133
+ query GetStorefrontProductBySlug($slug: String!) {
134
+ storefront_product_by_slug(slug: $slug) {
135
+ ...ProductFields
136
+ }
137
+ }
138
+
139
+ query GetStorefrontPopular($page: Int!, $pageSize: Int!) {
140
+ storefront_popular(page: $page, pageSize: $pageSize) {
141
+ items {
142
+ ...ProductFields
143
+ }
144
+ total
145
+ page
146
+ page_size
147
+ total_pages
148
+ }
149
+ }
150
+
151
+ query GetStorefrontFeatured($page: Int!, $pageSize: Int!) {
152
+ storefront_featured(page: $page, pageSize: $pageSize) {
153
+ items {
154
+ ...ProductFields
155
+ }
156
+ total
157
+ page
158
+ page_size
159
+ total_pages
160
+ }
161
+ }
162
+
163
+ query GetStorefrontNew($page: Int!, $pageSize: Int!) {
164
+ storefront_new(page: $page, pageSize: $pageSize) {
165
+ items {
166
+ ...ProductFields
167
+ }
168
+ total
169
+ page
170
+ page_size
171
+ total_pages
172
+ }
173
+ }
174
+
175
+ query GetStorefrontOnSale($page: Int!, $pageSize: Int!) {
176
+ storefront_on_sale(page: $page, pageSize: $pageSize) {
177
+ items {
178
+ ...ProductFields
179
+ }
180
+ total
181
+ page
182
+ page_size
183
+ total_pages
184
+ }
185
+ }
186
+
187
+ query GetStorefrontRecommended($page: Int!, $pageSize: Int!, $excludeProductId: String) {
188
+ storefront_recommended(page: $page, pageSize: $pageSize, excludeProductId: $excludeProductId) {
189
+ items {
190
+ ...ProductFields
191
+ }
192
+ total
193
+ page
194
+ page_size
195
+ total_pages
196
+ }
197
+ }
@@ -0,0 +1,25 @@
1
+ query GetStorefrontWishlist($page: Int!, $pageSize: Int!) {
2
+ storefront_wishlist(page: $page, pageSize: $pageSize) {
3
+ items {
4
+ id
5
+ product_id
6
+ notes
7
+ created_at
8
+ product_name
9
+ product_slug
10
+ product_price
11
+ currency
12
+ product_image
13
+ }
14
+ total
15
+ page
16
+ page_size
17
+ total_pages
18
+ }
19
+ }
20
+
21
+ query CheckInStorefrontWishlist($productId: String!) {
22
+ check_in_storefront_wishlist(productId: $productId) {
23
+ in_wishlist
24
+ }
25
+ }
@@ -6,6 +6,9 @@
6
6
 
7
7
  export * from './use-cart'
8
8
  export * from './use-orders'
9
+ export * from './use-consultations'
10
+ export * from './use-feedback'
11
+ export * from './use-customer-address'
9
12
  export * from './use-auth'
10
13
  export * from './use-countries'
11
14
  export * from './use-states'
@@ -16,13 +19,17 @@ export * from './use-collections'
16
19
  export * from './use-products'
17
20
  export * from './use-wishlist'
18
21
  export * from './use-service-booking'
19
- export * from './use-consultations'
20
- export * from './use-consultation-stream'
21
22
  export * from './use-order-stream'
23
+ export * from './use-reviews'
22
24
  export * from './use-is-mounted'
23
25
  export * from './use-mobile'
24
26
  export * from './use-google-translate'
25
27
  export * from './use-payment'
26
28
  export * from './use-cms'
29
+ export * from './use-business-config'
30
+ export * from './use-notifications'
31
+ export * from './use-notification-stream'
32
+ export * from './use-push-notifications'
33
+ export * from './use-media-upload'
27
34
  export * from '../context/sdk-context'
28
35
 
@@ -6,13 +6,34 @@
6
6
  * tokens and passes config to these hooks.
7
7
  */
8
8
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
9
- import { getAuthUser, loginCustomer, registerCustomer, updateProfile, logout as logoutService } from '../services/auth.service'
10
- import { logger } from '../utils/logger'
9
+ import {
10
+ getAuthUser,
11
+ loginCustomer,
12
+ registerCustomer,
13
+ requestCustomerOtp,
14
+ verifyCustomerOtp,
15
+ updateProfile,
16
+ logout as logoutService,
17
+ forgotPasswordCustomer,
18
+ resetPasswordCustomer,
19
+ deactivateCustomerAccount,
20
+ requestCustomerReactivation,
21
+ verifyCustomerReactivation,
22
+ getStorefrontBusinessInfo,
23
+ changePassword,
24
+ } from '../services/auth.service'
11
25
  import type {
12
26
  CustomerLoginPayload,
13
27
  CustomerRegisterPayload,
28
+ CustomerPasswordlessRequestPayload,
29
+ CustomerPasswordlessVerifyPayload,
30
+ CustomerForgotPasswordPayload,
31
+ CustomerResetPasswordPayload,
32
+ CustomerDeactivatePayload,
33
+ CustomerReactivatePayload,
34
+ CustomerReactivateVerifyPayload,
14
35
  UpdateProfilePayload,
15
- CustomerUser,
36
+ ChangePasswordPayload,
16
37
  } from '../types/auth'
17
38
  import type { SdkConfig } from '../config'
18
39
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
@@ -21,86 +42,159 @@ import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
21
42
 
22
43
  export function useAuthUser(config?: SdkConfig, accessToken?: string) {
23
44
  const ctx = useSdkContext()
24
- try {
25
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken ?? null, ctx)
26
- return useQuery({
27
- queryKey: ['auth-user'],
28
- queryFn: () => getAuthUser(cfg, token ?? ''),
29
- enabled: !!token,
30
- })
31
- } catch (error) {
32
- logger.warn('[useAuthUser] Called outside QueryClientProvider, returning empty user')
33
- return { data: undefined, isLoading: false, error: null, isFetching: false, refetch: () => Promise.resolve({ data: undefined }) } as any
34
- }
45
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken ?? null, ctx)
46
+ return useQuery({
47
+ queryKey: ['auth-user'],
48
+ queryFn: () => getAuthUser(cfg, token ?? ''),
49
+ enabled: !!token,
50
+ })
51
+ }
52
+
53
+ /**
54
+ * Real gap fixed 2026-09-17: GET /auth/storefront/business-info/ had no SDK
55
+ * hook — public/unauthenticated (only needs the storefront key), so unlike
56
+ * useAuthUser this is always enabled, never gated on an access token.
57
+ */
58
+ export function useStorefrontBusinessInfo(config?: SdkConfig) {
59
+ const ctx = useSdkContext()
60
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
61
+ return useQuery({
62
+ queryKey: ['storefront-business-info', cfg.businessId],
63
+ queryFn: () => getStorefrontBusinessInfo(cfg),
64
+ staleTime: 5 * 60 * 1000, // business branding info changes rarely
65
+ })
35
66
  }
36
67
 
37
68
  export function useLogin(config?: SdkConfig) {
38
69
  const ctx = useSdkContext()
39
- try {
40
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
41
- const queryClient = useQueryClient()
42
- return useMutation({
43
- mutationFn: (payload: Omit<CustomerLoginPayload, 'tenant_id'>) => loginCustomer(cfg, payload),
44
- onSuccess: () => {
45
- queryClient.invalidateQueries({ queryKey: ['auth-user'] })
46
- queryClient.invalidateQueries({ queryKey: ['cart'] })
47
- },
48
- })
49
- } catch (error) {
50
- logger.warn('[useLogin] Called outside QueryClientProvider, returning no-op mutation')
51
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
52
- }
70
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
71
+ const queryClient = useQueryClient()
72
+ return useMutation({
73
+ mutationFn: (payload: CustomerLoginPayload) => loginCustomer(cfg, payload),
74
+ onSuccess: () => {
75
+ queryClient.invalidateQueries({ queryKey: ['auth-user'] })
76
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
77
+ },
78
+ })
53
79
  }
54
80
 
55
81
  export function useRegister(config?: SdkConfig) {
56
82
  const ctx = useSdkContext()
57
- try {
58
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
59
- const queryClient = useQueryClient()
60
- return useMutation({
61
- mutationFn: (payload: Omit<CustomerRegisterPayload, 'tenant_id'>) => registerCustomer(cfg, payload),
62
- onSuccess: () => {
63
- queryClient.invalidateQueries({ queryKey: ['auth-user'] })
64
- queryClient.invalidateQueries({ queryKey: ['cart'] })
65
- },
66
- })
67
- } catch (error) {
68
- logger.warn('[useRegister] Called outside QueryClientProvider, returning no-op mutation')
69
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
70
- }
83
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
84
+ const queryClient = useQueryClient()
85
+ return useMutation({
86
+ mutationFn: (payload: CustomerRegisterPayload) => registerCustomer(cfg, payload),
87
+ onSuccess: () => {
88
+ queryClient.invalidateQueries({ queryKey: ['auth-user'] })
89
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
90
+ },
91
+ })
92
+ }
93
+
94
+ export function useRequestOtp(config?: SdkConfig) {
95
+ const ctx = useSdkContext()
96
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
97
+ return useMutation({
98
+ mutationFn: (payload: CustomerPasswordlessRequestPayload) => requestCustomerOtp(cfg, payload),
99
+ })
100
+ }
101
+
102
+ export function useVerifyOtp(config?: SdkConfig) {
103
+ const ctx = useSdkContext()
104
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
105
+ const queryClient = useQueryClient()
106
+ return useMutation({
107
+ mutationFn: (payload: CustomerPasswordlessVerifyPayload) => verifyCustomerOtp(cfg, payload),
108
+ onSuccess: () => {
109
+ queryClient.invalidateQueries({ queryKey: ['auth-user'] })
110
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
111
+ },
112
+ })
113
+ }
114
+
115
+ export function useCustomerForgotPassword(config?: SdkConfig) {
116
+ const ctx = useSdkContext()
117
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
118
+ return useMutation({
119
+ mutationFn: (payload: CustomerForgotPasswordPayload) => forgotPasswordCustomer(cfg, payload),
120
+ })
121
+ }
122
+
123
+ export function useCustomerResetPassword(config?: SdkConfig) {
124
+ const ctx = useSdkContext()
125
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
126
+ return useMutation({
127
+ mutationFn: (payload: CustomerResetPasswordPayload) => resetPasswordCustomer(cfg, payload),
128
+ })
129
+ }
130
+
131
+ export function useCustomerDeactivateAccount(config?: SdkConfig, accessToken?: string | null) {
132
+ const ctx = useSdkContext()
133
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
134
+ const queryClient = useQueryClient()
135
+ return useMutation({
136
+ mutationFn: (payload?: CustomerDeactivatePayload) =>
137
+ token ? deactivateCustomerAccount(cfg, token, payload) : Promise.reject(new Error('No access token available')),
138
+ onSuccess: () => {
139
+ queryClient.clear()
140
+ },
141
+ })
142
+ }
143
+
144
+ export function useCustomerReactivateRequest(config?: SdkConfig) {
145
+ const ctx = useSdkContext()
146
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
147
+ return useMutation({
148
+ mutationFn: (payload: CustomerReactivatePayload) => requestCustomerReactivation(cfg, payload),
149
+ })
150
+ }
151
+
152
+ export function useCustomerReactivateVerify(config?: SdkConfig) {
153
+ const ctx = useSdkContext()
154
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
155
+ return useMutation({
156
+ mutationFn: (payload: CustomerReactivateVerifyPayload) => verifyCustomerReactivation(cfg, payload),
157
+ })
71
158
  }
72
159
 
73
160
  export function useUpdateProfile(config?: SdkConfig, accessToken?: string | null) {
74
161
  const ctx = useSdkContext()
75
- try {
76
- const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
77
- const queryClient = useQueryClient()
78
- return useMutation({
79
- mutationFn: (payload: UpdateProfilePayload) =>
80
- token ? updateProfile(cfg, token, payload) : Promise.reject('No access token'),
81
- onSuccess: () => {
82
- queryClient.invalidateQueries({ queryKey: ['auth-user'] })
83
- },
84
- })
85
- } catch (error) {
86
- logger.warn('[useUpdateProfile] Called outside QueryClientProvider, returning no-op mutation')
87
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
88
- }
162
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
163
+ const queryClient = useQueryClient()
164
+ return useMutation({
165
+ mutationFn: (payload: UpdateProfilePayload) =>
166
+ token ? updateProfile(cfg, token, payload) : Promise.reject(new Error('No access token available')),
167
+ onSuccess: () => {
168
+ queryClient.invalidateQueries({ queryKey: ['auth-user'] })
169
+ },
170
+ })
171
+ }
172
+
173
+ /**
174
+ * POST /auth/change-password/ — REAL GAP FIXED (2026-09-20): this endpoint
175
+ * is fully customer-reachable on the real backend (ChangePasswordView
176
+ * branches on identity_mode) but had zero SDK coverage at all until now —
177
+ * no storefront customer had any way to change their password once logged
178
+ * in (only the logged-out forgot/reset-password OTP flow existed).
179
+ */
180
+ export function useChangePassword(config?: SdkConfig, accessToken?: string | null) {
181
+ const ctx = useSdkContext()
182
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
183
+ return useMutation({
184
+ mutationFn: (payload: ChangePasswordPayload) =>
185
+ token ? changePassword(cfg, token, payload) : Promise.reject(new Error('No access token available')),
186
+ })
89
187
  }
90
188
 
91
189
  export function useLogout(config?: SdkConfig, refreshToken?: string) {
92
190
  const ctx = useSdkContext()
93
- try {
94
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
95
- const queryClient = useQueryClient()
96
- return useMutation({
97
- mutationFn: () => logoutService(cfg, refreshToken),
98
- onSuccess: () => {
99
- queryClient.clear()
100
- },
101
- })
102
- } catch (error) {
103
- logger.warn('[useLogout] Called outside QueryClientProvider, returning no-op mutation')
104
- return { mutate: () => {}, mutateAsync: () => Promise.resolve(undefined) } as any
105
- }
191
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
192
+ const queryClient = useQueryClient()
193
+ return useMutation({
194
+ mutationFn: () => logoutService(cfg, refreshToken),
195
+ onSuccess: () => {
196
+ queryClient.clear()
197
+ },
198
+ })
106
199
  }
200
+
@@ -4,11 +4,9 @@
4
4
  * React Query hook for checking bespoke service availability.
5
5
  * Stateless - requires config parameter.
6
6
  */
7
- import { useQuery } from '@tanstack/react-query'
8
- import { checkAvailability } from '../services/bespoke.service'
7
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
8
+ import { checkAvailability, approveBespokeDeliverable, requestBespokeRevision } from '../services/bespoke.service'
9
9
  import type { SdkConfig } from '../config'
10
- import type { BespokeAvailability } from '../types/consultations'
11
- import { logger } from '../utils'
12
10
  import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
13
11
 
14
12
  /**
@@ -16,22 +14,41 @@ import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
16
14
  */
17
15
  export function useBespokeAvailability(product_id: string, config?: SdkConfig) {
18
16
  const ctx = useSdkContext()
19
- try {
20
- const { config: cfg } = resolveHookArgs(config, undefined, ctx)
21
- return useQuery({
22
- queryKey: ['bespoke-availability', product_id],
23
- queryFn: () => checkAvailability(cfg, product_id),
24
- enabled: !!product_id,
25
- retry: false,
26
- })
27
- } catch (error) {
28
- logger.warn('[useBespokeAvailability] Called outside QueryClientProvider, returning undefined')
29
- return {
30
- data: undefined,
31
- isLoading: false,
32
- error: null,
33
- isFetching: false,
34
- refetch: () => Promise.resolve({ data: undefined }),
35
- } as any
36
- }
17
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
18
+ return useQuery({
19
+ queryKey: ['bespoke-availability', product_id],
20
+ queryFn: () => checkAvailability(cfg, product_id),
21
+ enabled: !!product_id,
22
+ retry: false,
23
+ })
24
+ }
25
+
26
+ /** Buyer approves a bespoke order's submitted deliverable. */
27
+ export function useApproveBespokeDeliverable(config?: SdkConfig, accessToken?: string | null) {
28
+ const ctx = useSdkContext()
29
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
30
+ const queryClient = useQueryClient()
31
+ return useMutation({
32
+ mutationFn: (orderId: string) =>
33
+ token ? approveBespokeDeliverable(cfg, token, orderId) : Promise.reject(new Error('No access token available')),
34
+ onSuccess: (_data, orderId) => {
35
+ queryClient.invalidateQueries({ queryKey: ['order', orderId] })
36
+ queryClient.invalidateQueries({ queryKey: ['orders'] })
37
+ },
38
+ })
39
+ }
40
+
41
+ /** Buyer requests a revision on a bespoke order's submitted deliverable. */
42
+ export function useRequestBespokeRevision(config?: SdkConfig, accessToken?: string | null) {
43
+ const ctx = useSdkContext()
44
+ const { config: cfg, accessToken: token } = resolveHookArgs(config, accessToken, ctx)
45
+ const queryClient = useQueryClient()
46
+ return useMutation({
47
+ mutationFn: ({ orderId, notes }: { orderId: string; notes: string }) =>
48
+ token ? requestBespokeRevision(cfg, token, orderId, notes) : Promise.reject(new Error('No access token available')),
49
+ onSuccess: (_data, variables) => {
50
+ queryClient.invalidateQueries({ queryKey: ['order', variables.orderId] })
51
+ queryClient.invalidateQueries({ queryKey: ['orders'] })
52
+ },
53
+ })
37
54
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Business Config Hook
3
+ *
4
+ * Resolves the business's internal UUID (and currency/branding fields) via
5
+ * kasuvia-auth's internal business-detail endpoint. Several other backends
6
+ * (kasuvia-cms's SDK routes, notably) key their data by this UUID rather
7
+ * than the public BID a storefront's own config carries — this hook is the
8
+ * shared, cached way to resolve it instead of every caller re-fetching.
9
+ * Cached aggressively: a business's UUID never changes.
10
+ */
11
+ import { useCallback } from 'react'
12
+ import { useQuery } from '@tanstack/react-query'
13
+ import { getBusinessConfig } from '../services/config.service'
14
+ import type { SdkConfig } from '../config'
15
+ import { useSdkContext, resolveHookArgs } from '../context/sdk-context'
16
+ import { formatPrice as formatPriceFn } from '../utils/format'
17
+
18
+ export function useBusinessConfig(config?: SdkConfig) {
19
+ const ctx = useSdkContext()
20
+ const { config: cfg } = resolveHookArgs(config, undefined, ctx)
21
+ return useQuery({
22
+ queryKey: ['business-config', cfg.businessId],
23
+ queryFn: () => getBusinessConfig(cfg),
24
+ staleTime: Infinity,
25
+ gcTime: Infinity,
26
+ })
27
+ }
28
+
29
+ /**
30
+ * REAL GAP FIXED (2026-09-22): `formatPrice(amount, currency)` is stateless
31
+ * and requires the caller to already have the real business currency in
32
+ * hand — every consuming app was left to resolve that currency itself, and
33
+ * kasuvia-storefront's `AppProvider` ended up hand-rolling its own,
34
+ * slightly different copy of the same `Intl.NumberFormat` currency logic
35
+ * (silently falling back to a bare number instead of formatPrice's
36
+ * explicit "N/A" marker when currency is unresolved). This hook is the one
37
+ * real, complete answer: resolves the business's currency via
38
+ * `useBusinessConfig` and returns a ready-to-call formatter, so no
39
+ * consuming app ever needs to reimplement currency formatting again.
40
+ */
41
+ export function useFormatPrice(config?: SdkConfig) {
42
+ const { data: businessConfig } = useBusinessConfig(config)
43
+ const currency = businessConfig?.currency ?? null
44
+
45
+ const formatPrice = useCallback(
46
+ (amount: number | null | undefined) => formatPriceFn(amount, currency),
47
+ [currency]
48
+ )
49
+
50
+ return { formatPrice, currency }
51
+ }