@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
@@ -1,9 +1,23 @@
1
1
  /**
2
2
  * CMS Service
3
- *
3
+ *
4
4
  * Handles CMS model and entry operations using the kasuvia-cms SDK endpoints.
5
5
  * Protected by X-Storefront-Key and X-Business-ID headers.
6
6
  * Stateless - all configuration passed as parameters.
7
+ *
8
+ * REAL BUG FIXED (2026-09-21), live-reproduced: every URL here used
9
+ * `config.businessId` (the public BID, e.g. "B-2W9EC07") as the
10
+ * `{business_id}` path segment. kasuvia-cms's SDK routes
11
+ * (`app/core/security/base.py`'s `get_storefront_business_uuid`, used by
12
+ * every route in `cms_sdk.py`) explicitly require the INTERNAL UUID there —
13
+ * "CmsModel/CmsEntry documents are keyed by the UUID business_id... SDK
14
+ * routes need this, not the BID" (its own docstring) — and 403s with
15
+ * "Business ID mismatch" on anything else. Every CMS entry call from the
16
+ * storefront has been failing since the feature was built. Fixed by
17
+ * requiring an explicit `businessUuid` parameter (resolved via
18
+ * `getBusinessConfig`'s real `business_id` field, cached by
19
+ * `useBusinessConfig()` at the hook layer) instead of silently reusing the
20
+ * wrong config field.
7
21
  */
8
22
  import type { SdkConfig } from '../config'
9
23
  import type {
@@ -13,6 +27,7 @@ import type {
13
27
  UpdateCmsEntryParams,
14
28
  ListCmsEntriesParams,
15
29
  } from '../types/cms'
30
+ import { apiRequest, storefrontHeaders, authHeaders, JSON_HEADERS } from '../utils/http-client'
16
31
 
17
32
  /**
18
33
  * List CMS entries for a specific model.
@@ -20,6 +35,7 @@ import type {
20
35
  */
21
36
  export async function listCmsEntries(
22
37
  config: SdkConfig,
38
+ businessUuid: string,
23
39
  modelSlug: string,
24
40
  params?: ListCmsEntriesParams
25
41
  ): Promise<CmsEntryListResponse> {
@@ -27,18 +43,13 @@ export async function listCmsEntries(
27
43
  queryParams.set('page', (params?.page || 1).toString())
28
44
  queryParams.set('page_size', (params?.page_size || 50).toString())
29
45
 
30
- const response = await fetch(
31
- `${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries?${queryParams.toString()}`,
46
+ return apiRequest<CmsEntryListResponse>(
47
+ `${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries?${queryParams.toString()}`,
32
48
  {
33
- headers: {
34
- "X-Storefront-Key": config.storefrontApiKey,
35
- "X-Business-ID": config.businessId,
36
- },
49
+ headers: storefrontHeaders(config),
50
+ fallbackErrorMessage: 'Failed to list CMS entries',
37
51
  }
38
52
  )
39
-
40
- if (!response.ok) throw new Error('Failed to list CMS entries')
41
- return response.json()
42
53
  }
43
54
 
44
55
  /**
@@ -47,21 +58,17 @@ export async function listCmsEntries(
47
58
  */
48
59
  export async function getCmsEntry(
49
60
  config: SdkConfig,
61
+ businessUuid: string,
50
62
  modelSlug: string,
51
63
  entryId: string
52
64
  ): Promise<CmsEntry> {
53
- const response = await fetch(
54
- `${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
65
+ return apiRequest<CmsEntry>(
66
+ `${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries/${entryId}`,
55
67
  {
56
- headers: {
57
- "X-Storefront-Key": config.storefrontApiKey,
58
- "X-Business-ID": config.businessId,
59
- },
68
+ headers: storefrontHeaders(config),
69
+ fallbackErrorMessage: 'Failed to get CMS entry',
60
70
  }
61
71
  )
62
-
63
- if (!response.ok) throw new Error('Failed to get CMS entry')
64
- return response.json()
65
72
  }
66
73
 
67
74
  /**
@@ -70,26 +77,20 @@ export async function getCmsEntry(
70
77
  */
71
78
  export async function createCmsEntry(
72
79
  config: SdkConfig,
80
+ businessUuid: string,
73
81
  accessToken: string,
74
82
  modelSlug: string,
75
83
  params: CreateCmsEntryParams
76
84
  ): Promise<CmsEntry> {
77
- const response = await fetch(
78
- `${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries`,
85
+ return apiRequest<CmsEntry>(
86
+ `${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries`,
79
87
  {
80
88
  method: 'POST',
81
- headers: {
82
- 'Content-Type': 'application/json',
83
- "Authorization": `Bearer ${accessToken}`,
84
- "X-Storefront-Key": config.storefrontApiKey,
85
- "X-Business-ID": config.businessId,
86
- },
89
+ headers: { ...JSON_HEADERS, ...authHeaders(config, accessToken) },
87
90
  body: JSON.stringify(params),
91
+ fallbackErrorMessage: 'Failed to create CMS entry',
88
92
  }
89
93
  )
90
-
91
- if (!response.ok) throw new Error('Failed to create CMS entry')
92
- return response.json()
93
94
  }
94
95
 
95
96
  /**
@@ -98,27 +99,21 @@ export async function createCmsEntry(
98
99
  */
99
100
  export async function updateCmsEntry(
100
101
  config: SdkConfig,
102
+ businessUuid: string,
101
103
  accessToken: string,
102
104
  modelSlug: string,
103
105
  entryId: string,
104
106
  params: UpdateCmsEntryParams
105
107
  ): Promise<CmsEntry> {
106
- const response = await fetch(
107
- `${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
108
+ return apiRequest<CmsEntry>(
109
+ `${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries/${entryId}`,
108
110
  {
109
111
  method: 'PUT',
110
- headers: {
111
- 'Content-Type': 'application/json',
112
- "Authorization": `Bearer ${accessToken}`,
113
- "X-Storefront-Key": config.storefrontApiKey,
114
- "X-Business-ID": config.businessId,
115
- },
112
+ headers: { ...JSON_HEADERS, ...authHeaders(config, accessToken) },
116
113
  body: JSON.stringify(params),
114
+ fallbackErrorMessage: 'Failed to update CMS entry',
117
115
  }
118
116
  )
119
-
120
- if (!response.ok) throw new Error('Failed to update CMS entry')
121
- return response.json()
122
117
  }
123
118
 
124
119
  /**
@@ -127,21 +122,17 @@ export async function updateCmsEntry(
127
122
  */
128
123
  export async function deleteCmsEntry(
129
124
  config: SdkConfig,
125
+ businessUuid: string,
130
126
  accessToken: string,
131
127
  modelSlug: string,
132
128
  entryId: string
133
129
  ): Promise<void> {
134
- const response = await fetch(
135
- `${config.backendUrls.cms}/sdk/${config.businessId}/${modelSlug}/entries/${entryId}`,
130
+ await apiRequest(
131
+ `${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries/${entryId}`,
136
132
  {
137
133
  method: 'DELETE',
138
- headers: {
139
- "Authorization": `Bearer ${accessToken}`,
140
- "X-Storefront-Key": config.storefrontApiKey,
141
- "X-Business-ID": config.businessId,
142
- },
134
+ headers: authHeaders(config, accessToken),
135
+ fallbackErrorMessage: 'Failed to delete CMS entry',
143
136
  }
144
137
  )
145
-
146
- if (!response.ok) throw new Error('Failed to delete CMS entry')
147
138
  }
@@ -1,68 +1,28 @@
1
1
  /**
2
2
  * Collection Service
3
- *
3
+ *
4
4
  * Handles collection operations using GraphQL.
5
5
  * Stateless - all configuration passed as parameters.
6
6
  */
7
- import { GraphQLClient } from 'graphql-request'
8
7
  import type { Collection, PaginatedResponse, ListParams } from '../types/store'
9
8
  import type { SdkConfig } from '../config'
9
+ import { createGraphQLClient } from '../utils/graphql-url'
10
+ import {
11
+ GetStorefrontCollectionsDocument,
12
+ GetStorefrontCollectionBySlugDocument,
13
+ type CollectionFieldsFragment,
14
+ } from '../generated/graphql'
10
15
 
11
- const GET_STOREFRONT_COLLECTIONS = `
12
- query GetStorefrontCollections($page: Int!, $pageSize: Int!) {
13
- storefront_collections(page: $page, pageSize: $pageSize) {
14
- items {
15
- id
16
- business_id
17
- name
18
- slug
19
- description
20
- image
21
- collection_type
22
- rules
23
- status
24
- sort_order
25
- created_at
26
- updated_at
27
- }
28
- total
29
- page
30
- page_size
31
- total_pages
32
- }
33
- }
34
- `
35
-
36
- const GET_STOREFRONT_COLLECTION_BY_SLUG = `
37
- query GetStorefrontCollectionBySlug($slug: String!) {
38
- storefront_collection_by_slug(slug: $slug) {
39
- id
40
- business_id
41
- name
42
- slug
43
- description
44
- image
45
- collection_type
46
- rules
47
- status
48
- sort_order
49
- created_at
50
- updated_at
51
- }
52
- }
53
- `
54
-
55
- /**
56
- * Create GraphQL client with provided config
57
- */
58
- function createGraphQLClient(config: SdkConfig): GraphQLClient {
59
- const headers: Record<string, string> = {
60
- 'Content-Type': 'application/json',
61
- 'Accept': 'application/json',
62
- 'X-Storefront-Key': config.storefrontApiKey,
63
- 'X-Business-ID': config.businessId,
64
- }
65
- return new GraphQLClient(`${config.backendUrls.store}/graphql`, { headers })
16
+ function mapCollection(raw: CollectionFieldsFragment): Collection {
17
+ return {
18
+ ...raw,
19
+ business_id: raw.business_id ?? undefined,
20
+ description: raw.description ?? undefined,
21
+ image_url: raw.image_url ?? undefined,
22
+ rules: raw.rules ?? undefined,
23
+ created_at: raw.created_at as string,
24
+ updated_at: raw.updated_at as string,
25
+ } satisfies Collection
66
26
  }
67
27
 
68
28
  /**
@@ -73,12 +33,11 @@ export async function getStorefrontCollections(
73
33
  params?: ListParams
74
34
  ): Promise<PaginatedResponse<Collection>> {
75
35
  const client = createGraphQLClient(config)
76
- const variables = {
36
+ const data = await client.request(GetStorefrontCollectionsDocument, {
77
37
  page: params?.page || 1,
78
38
  pageSize: params?.pageSize || 20,
79
- }
80
- const data = await client.request(GET_STOREFRONT_COLLECTIONS, variables)
81
- return data.storefront_collections
39
+ })
40
+ return { ...data.storefront_collections, items: data.storefront_collections.items.map(mapCollection) }
82
41
  }
83
42
 
84
43
  /**
@@ -89,6 +48,9 @@ export async function getStorefrontCollectionBySlug(
89
48
  slug: string
90
49
  ): Promise<Collection> {
91
50
  const client = createGraphQLClient(config)
92
- const data = await client.request(GET_STOREFRONT_COLLECTION_BY_SLUG, { slug })
93
- return data.storefront_collection_by_slug
51
+ const data = await client.request(GetStorefrontCollectionBySlugDocument, { slug })
52
+ if (!data.storefront_collection_by_slug) {
53
+ throw new Error(`Collection not found: ${slug}`)
54
+ }
55
+ return mapCollection(data.storefront_collection_by_slug)
94
56
  }
@@ -7,20 +7,17 @@
7
7
  import type { SdkConfig } from '../config'
8
8
  import type { BackendLanguage, SupportedLanguagesResponse, SupportedCurrenciesResponse } from '../types/payments'
9
9
  import type { BusinessConfig } from '../types/business'
10
+ import { apiRequest, storefrontHeaders } from '../utils/http-client'
10
11
 
11
12
  /**
12
13
  * Fetch supported languages from the payment system.
13
14
  * Resolves to: {kasuviaPaymentBackendUrl}/config/supported-languages
14
15
  */
15
16
  export async function getSupportedLanguages(config: SdkConfig): Promise<SupportedLanguagesResponse> {
16
- const response = await fetch(`${config.backendUrls.payment}/config/supported-languages`, {
17
- headers: {
18
- "X-Storefront-Key": config.storefrontApiKey,
19
- "X-Business-ID": config.businessId,
20
- },
17
+ return apiRequest<SupportedLanguagesResponse>(`${config.backendUrls.payment}/config/supported-languages`, {
18
+ headers: storefrontHeaders(config),
19
+ fallbackErrorMessage: 'Failed to get supported languages',
21
20
  })
22
- if (!response.ok) throw new Error('Failed to get supported languages')
23
- return response.json()
24
21
  }
25
22
 
26
23
  /**
@@ -28,14 +25,10 @@ export async function getSupportedLanguages(config: SdkConfig): Promise<Supporte
28
25
  * Resolves to: {kasuviaPaymentBackendUrl}/config/supported-currencies
29
26
  */
30
27
  export async function getSupportedCurrencies(config: SdkConfig): Promise<SupportedCurrenciesResponse> {
31
- const response = await fetch(`${config.backendUrls.payment}/config/supported-currencies`, {
32
- headers: {
33
- "X-Storefront-Key": config.storefrontApiKey,
34
- "X-Business-ID": config.businessId,
35
- },
28
+ return apiRequest<SupportedCurrenciesResponse>(`${config.backendUrls.payment}/config/supported-currencies`, {
29
+ headers: storefrontHeaders(config),
30
+ fallbackErrorMessage: 'Failed to get supported currencies',
36
31
  })
37
- if (!response.ok) throw new Error('Failed to get supported currencies')
38
- return response.json()
39
32
  }
40
33
 
41
34
  /**
@@ -45,14 +38,10 @@ export async function getSupportedCurrencies(config: SdkConfig): Promise<Support
45
38
  * This endpoint is designed for storefronts to fetch business config using their storefront API key.
46
39
  */
47
40
  export async function getBusinessConfig(config: SdkConfig): Promise<BusinessConfig> {
48
- const response = await fetch(`${config.backendUrls.auth}/internal/businesses/${config.businessId}/`, {
49
- headers: {
50
- "X-Storefront-Key": config.storefrontApiKey,
51
- "X-Business-ID": config.businessId,
52
- },
41
+ return apiRequest<BusinessConfig>(`${config.backendUrls.auth}/internal/businesses/${config.businessId}/`, {
42
+ headers: storefrontHeaders(config),
43
+ fallbackErrorMessage: 'Failed to get business config',
53
44
  })
54
- if (!response.ok) throw new Error('Failed to get business config')
55
- return response.json()
56
45
  }
57
46
 
58
47
  export const configService = {
@@ -1,105 +1,48 @@
1
1
  /**
2
2
  * Consultation Service
3
- *
4
- * Handles consultation operations for bespoke and service products.
5
- * Stateless - all configuration passed as parameters.
3
+ *
4
+ * REAL BUG FIXED (2026-09-21): a "consultation" service existed earlier this
5
+ * session pointed at a fictional `/social/consultations/...` route that
6
+ * matched nothing in any backend — deleted along with its dead storefront
7
+ * pages. That deletion was correct (the URL was wrong), but a later audit
8
+ * of kasuvia-crm-go found the underlying feature is real:
9
+ * `POST /api/v1/consultations/` (customer-JWT-gated, real bespoke-slot-
10
+ * reservation side effect when product_id references a bespoke product).
11
+ * There is deliberately no list/get function here — kasuvia-crm-go's own
12
+ * code comment confirms "there is no customer-facing GET/list consultation
13
+ * endpoint at all" (admin_consultations.go). Create-only, by design.
6
14
  */
7
15
  import type { SdkConfig } from '../config'
8
- import type {
9
- Consultation,
10
- ConsultationCreatePayload,
11
- ConsultationMessagePayload,
12
- ConsultationQuoteAcceptPayload,
13
- } from '../types/consultations'
16
+ import { apiRequest, authHeaders, JSON_HEADERS } from '../utils/http-client'
14
17
 
15
- export async function createConsultation(config: SdkConfig, accessToken: string, payload: ConsultationCreatePayload): Promise<Consultation> {
16
- const response = await fetch(`${config.backendUrls.store}/social/consultations/`, {
17
- method: 'POST',
18
- headers: {
19
- 'Content-Type': 'application/json',
20
- "Authorization": `Bearer ${accessToken}`,
21
- "X-Storefront-Key": config.storefrontApiKey,
22
- "X-Business-ID": config.businessId,
23
- },
24
- body: JSON.stringify(payload),
25
- })
26
- if (!response.ok) throw new Error('Failed to create consultation')
27
- return response.json()
28
- }
29
-
30
- export async function getConsultations(config: SdkConfig, accessToken: string): Promise<Consultation[]> {
31
- const response = await fetch(`${config.backendUrls.store}/social/consultations/`, {
32
- headers: {
33
- "Authorization": `Bearer ${accessToken}`,
34
- "X-Storefront-Key": config.storefrontApiKey,
35
- "X-Business-ID": config.businessId,
36
- },
37
- })
38
- if (!response.ok) throw new Error('Failed to get consultations')
39
- return response.json()
40
- }
41
-
42
- export async function getConsultation(config: SdkConfig, accessToken: string, id: string): Promise<Consultation> {
43
- const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}`, {
44
- headers: {
45
- "Authorization": `Bearer ${accessToken}`,
46
- "X-Storefront-Key": config.storefrontApiKey,
47
- "X-Business-ID": config.businessId,
48
- },
49
- })
50
- if (!response.ok) throw new Error('Failed to get consultation')
51
- return response.json()
18
+ export interface CreateConsultationParams {
19
+ product_id?: string
20
+ structured_details?: Record<string, unknown>
52
21
  }
53
22
 
54
- export async function sendMessage(config: SdkConfig, accessToken: string, id: string, payload: ConsultationMessagePayload): Promise<void> {
55
- await fetch(`${config.backendUrls.store}/social/consultations/${id}/messages`, {
56
- method: 'POST',
57
- headers: {
58
- 'Content-Type': 'application/json',
59
- "Authorization": `Bearer ${accessToken}`,
60
- "X-Storefront-Key": config.storefrontApiKey,
61
- "X-Business-ID": config.businessId,
62
- },
63
- body: JSON.stringify(payload),
64
- })
65
- }
66
-
67
- export async function acceptQuote(config: SdkConfig, accessToken: string, id: string, payload: ConsultationQuoteAcceptPayload): Promise<Consultation> {
68
- const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}/accept-quote`, {
69
- method: 'POST',
70
- headers: {
71
- 'Content-Type': 'application/json',
72
- "Authorization": `Bearer ${accessToken}`,
73
- "X-Storefront-Key": config.storefrontApiKey,
74
- "X-Business-ID": config.businessId,
75
- },
76
- body: JSON.stringify(payload),
77
- })
78
- if (!response.ok) throw new Error('Failed to accept quote')
79
- return response.json()
23
+ export interface Consultation {
24
+ id: string
25
+ business_id: string
26
+ customer_id: string | null
27
+ product_id: string | null
28
+ status: string
29
+ quote_amount: number | null
30
+ structured_details: Record<string, unknown>
31
+ capacity_config_id: string | null
32
+ slot_booking_id: string | null
33
+ created_at: string
34
+ updated_at: string
80
35
  }
81
36
 
82
- export async function rejectQuote(config: SdkConfig, accessToken: string, id: string, reason?: string): Promise<Consultation> {
83
- const response = await fetch(`${config.backendUrls.store}/social/consultations/${id}/reject-quote`, {
37
+ export async function createConsultation(
38
+ config: SdkConfig,
39
+ accessToken: string,
40
+ params: CreateConsultationParams
41
+ ): Promise<Consultation> {
42
+ return apiRequest<Consultation>(`${config.backendUrls.crm}/consultations/`, {
84
43
  method: 'POST',
85
- headers: {
86
- 'Content-Type': 'application/json',
87
- "Authorization": `Bearer ${accessToken}`,
88
- "X-Storefront-Key": config.storefrontApiKey,
89
- "X-Business-ID": config.businessId,
90
- },
91
- body: JSON.stringify({ reason }),
44
+ headers: { ...JSON_HEADERS, ...authHeaders(config, accessToken) },
45
+ body: JSON.stringify(params),
46
+ fallbackErrorMessage: 'Failed to submit your request',
92
47
  })
93
- if (!response.ok) throw new Error('Failed to reject quote')
94
- return response.json()
95
- }
96
-
97
- // Service object export for backward compatibility
98
- export const consultationService = {
99
- createConsultation,
100
- getConsultations,
101
- getConsultation,
102
- sendMessage,
103
- acceptQuote,
104
- rejectQuote,
105
48
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Feedback Service
3
+ *
4
+ * REAL FEATURE WIRED (2026-09-21): kasuvia-crm-go's feedback.go (`POST
5
+ * /api/v1/feedback/`, `GET /api/v1/feedback/my`) is a complete, real,
6
+ * customer-JWT-gated support-ticket system — private business<->customer
7
+ * correspondence, distinct from a public, purchase-verified product Review —
8
+ * with zero SDK coverage anywhere before this file.
9
+ */
10
+ import type { SdkConfig } from '../config'
11
+ import { apiRequest, authHeaders, JSON_HEADERS } from '../utils/http-client'
12
+
13
+ /** Mirrors kasuvia-crm-go's models.ValidFeedbackCategories exactly. */
14
+ export type FeedbackCategory = 'general' | 'complaint' | 'suggestion'
15
+
16
+ /** Mirrors kasuvia-crm-go's models.ValidFeedbackStatuses exactly. */
17
+ export type FeedbackStatus = 'open' | 'in_progress' | 'resolved'
18
+
19
+ export interface CreateFeedbackParams {
20
+ subject: string
21
+ body: string
22
+ /** Defaults to 'general' server-side if omitted. */
23
+ category?: FeedbackCategory
24
+ order_id?: string
25
+ }
26
+
27
+ export interface Feedback {
28
+ id: string
29
+ business_id: string
30
+ customer_id: string
31
+ order_id: string | null
32
+ category: FeedbackCategory
33
+ subject: string
34
+ body: string
35
+ status: FeedbackStatus
36
+ merchant_reply: string | null
37
+ merchant_reply_at: string | null
38
+ replied_by: string | null
39
+ created_at: string
40
+ updated_at: string
41
+ }
42
+
43
+ export interface FeedbackListResponse {
44
+ results: Feedback[]
45
+ count: number
46
+ page: number
47
+ page_size: number
48
+ total_pages: number
49
+ }
50
+
51
+ export async function createFeedback(
52
+ config: SdkConfig,
53
+ accessToken: string,
54
+ params: CreateFeedbackParams
55
+ ): Promise<Feedback> {
56
+ return apiRequest<Feedback>(`${config.backendUrls.crm}/feedback/`, {
57
+ method: 'POST',
58
+ headers: { ...JSON_HEADERS, ...authHeaders(config, accessToken) },
59
+ body: JSON.stringify(params),
60
+ fallbackErrorMessage: 'Failed to submit your message',
61
+ })
62
+ }
63
+
64
+ export async function getMyFeedback(
65
+ config: SdkConfig,
66
+ accessToken: string,
67
+ params?: { page?: number; page_size?: number }
68
+ ): Promise<FeedbackListResponse> {
69
+ const query = new URLSearchParams()
70
+ if (params?.page) query.set('page', String(params.page))
71
+ if (params?.page_size) query.set('page_size', String(params.page_size))
72
+ return apiRequest<FeedbackListResponse>(`${config.backendUrls.crm}/feedback/my?${query.toString()}`, {
73
+ headers: authHeaders(config, accessToken),
74
+ fallbackErrorMessage: 'Failed to load your requests',
75
+ })
76
+ }
@@ -6,12 +6,13 @@
6
6
 
7
7
  export * from './cart.service'
8
8
  export * from './order.service'
9
+ export * from './consultation.service'
10
+ export * from './feedback.service'
9
11
  export * from './auth.service'
10
12
  export * from './product.service'
11
13
  export * from './collection.service'
12
14
  export * from './wishlist.service'
13
15
  export * from './payment.service'
14
- export * from './consultation.service'
15
16
  export * from './logistics.service'
16
17
  export * from './bespoke.service'
17
18
  export * from './service-booking.service'
@@ -19,3 +20,8 @@ export * from './notifications.service'
19
20
  export * from './app.service'
20
21
  export * from './config.service'
21
22
  export * from './cms.service'
23
+ export * from './legal.service'
24
+ export * from './review.service'
25
+ export * from './media.service'
26
+
27
+
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Legal Service
3
+ *
4
+ * Fetches this business's own currently-active storefront legal document
5
+ * (Terms, Privacy, or Refund Policy) from kasuvia-cms — public, no auth,
6
+ * the same endpoint the registration flow's consent record is checked against
7
+ * (see kasuvia-auth's CustomerTermsAcceptance).
8
+ *
9
+ * website-builder-system auto-generates storefront_terms/storefront_privacy
10
+ * the first time a business publishes (_ensure_storefront_legal_documents).
11
+ * A business that hasn't published yet, or whose auto-generation failed,
12
+ * genuinely has none yet. Returns null in that case rather than throwing;
13
+ * the page decides how to render that honestly — never substituting placeholder
14
+ * text for real legal text.
15
+ */
16
+ import type { SdkConfig } from '../config';
17
+ import type { MerchantLegalDocument } from '../types/cms/legal';
18
+
19
+ export type { MerchantLegalDocument };
20
+ export type { LegalSection } from '../types/cms/legal';
21
+
22
+ /**
23
+ * Fetch a storefront legal document from kasuvia-cms.
24
+ *
25
+ * REAL BUG FIXED (2026-09-22): took a raw `cmsBaseUrl` string instead of
26
+ * `SdkConfig` — the only function across this SDK's ~90 service functions
27
+ * that broke that convention, which is exactly why kasuvia-storefront's
28
+ * `lib/legal.ts` wrapper existed at all (bridging this one odd signature
29
+ * back to the app's real config). `businessUuid` is kept as its own
30
+ * explicit parameter rather than silently reading `config.businessId`:
31
+ * this real backend route (`GET /cms/legal/storefront/{business_id}/...`,
32
+ * confirmed against kasuvia-cms's `get_business_id` dependency and
33
+ * `MerchantLegalDocument.business_id` field) is keyed by the internal
34
+ * business UUID, never the public BID `config.businessId` actually is —
35
+ * the same UUID-vs-BID class of bug already found and fixed in
36
+ * `cms.service.ts`. Resolve it the same way (`useBusinessConfig`'s real
37
+ * `business_id` field) before calling this.
38
+ *
39
+ * @param config - SdkConfig; only `backendUrls.cms` is read from it here.
40
+ * @param businessUuid - The tenant's real business UUID (not the public BID).
41
+ * @param documentType - e.g. "storefront_terms", "storefront_privacy",
42
+ * "storefront_refund_policy"
43
+ * @param fetchOptions - Optional native fetch options (e.g. Next.js `next: { revalidate }`)
44
+ */
45
+ export async function getMerchantLegalDocument(
46
+ config: SdkConfig,
47
+ businessUuid: string,
48
+ documentType: string,
49
+ fetchOptions?: RequestInit,
50
+ ): Promise<MerchantLegalDocument | null> {
51
+ if (!businessUuid || !config?.backendUrls?.cms) return null;
52
+ try {
53
+ const res = await fetch(
54
+ `${config.backendUrls.cms}/cms/legal/storefront/${businessUuid}/${documentType}`,
55
+ fetchOptions,
56
+ );
57
+ if (!res.ok) return null;
58
+ return (await res.json()) as MerchantLegalDocument;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }