@kasuvia/sdk 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -15
- package/dist/AddressAutocomplete-CjcqPElD.d.mts +46 -0
- package/dist/AddressAutocomplete-CvJHuRkf.d.ts +46 -0
- package/dist/components/index.d.mts +497 -0
- package/dist/components/index.d.ts +497 -0
- package/dist/components/index.js +4217 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +4194 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/server.d.mts +26 -0
- package/dist/config/server.d.ts +26 -0
- package/dist/config/server.js +38 -0
- package/dist/config/server.js.map +1 -0
- package/dist/config/server.mjs +13 -0
- package/dist/config/server.mjs.map +1 -0
- package/dist/hooks/index.d.mts +393 -160
- package/dist/hooks/index.d.ts +393 -160
- package/dist/hooks/index.js +2792 -2137
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +2734 -2121
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index-CNW_LINy.d.mts +578 -0
- package/dist/index-CNW_LINy.d.ts +578 -0
- package/dist/{index-C1chUA3P.d.mts → index-F7N6UItS.d.mts} +1 -1
- package/dist/{index-C1chUA3P.d.ts → index-F7N6UItS.d.ts} +1 -1
- package/dist/index-NkI10J_E.d.mts +40 -0
- package/dist/index-NkI10J_E.d.ts +40 -0
- package/dist/index.d.mts +20437 -103
- package/dist/index.d.ts +20437 -103
- package/dist/index.js +2111 -1802
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2006 -1786
- package/dist/index.mjs.map +1 -1
- package/dist/media.service-Cx7dQjzX.d.mts +1393 -0
- package/dist/media.service-mrBFJ2UQ.d.ts +1393 -0
- package/dist/services/index.d.mts +908 -2
- package/dist/services/index.d.ts +908 -2
- package/dist/services/index.js +1492 -1686
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +1431 -1670
- package/dist/services/index.mjs.map +1 -1
- package/package.json +46 -6
- package/src/components/bespoke/BespokeAvailabilityBadge.tsx +32 -0
- package/src/components/bespoke/BespokeProgressTracker.tsx +58 -0
- package/src/components/bespoke/index.ts +3 -0
- package/src/components/booking/SlotPicker.tsx +97 -0
- package/src/components/booking/index.ts +1 -0
- package/src/components/cart/CartItem.tsx +81 -0
- package/src/components/cart/CartSidebar.tsx +115 -0
- package/src/components/cart/CartTypeMismatchBanner.tsx +55 -0
- package/src/components/cart/index.ts +3 -0
- package/src/components/index.ts +8 -0
- package/src/components/orders/DeliverableReviewer.tsx +139 -0
- package/src/components/orders/index.ts +1 -0
- package/src/components/product/ProductCard.tsx +350 -0
- package/src/components/product/ProductGrid.tsx +84 -0
- package/src/components/product/index.ts +2 -0
- package/src/components/shared/AddressAutocomplete.tsx +212 -0
- package/src/components/shared/AnalyticsScripts.tsx +56 -0
- package/src/components/shared/CookieConsentBanner.tsx +98 -0
- package/src/components/shared/ImageEditorModal.tsx +1066 -0
- package/src/components/shared/ImageViewerModal.tsx +598 -0
- package/src/components/shared/LucideReactIcon.tsx +52 -0
- package/src/components/shared/MapPinDrop.tsx +101 -0
- package/src/components/shared/MultiSelectPicker.tsx +193 -0
- package/src/components/shared/OfflineBanner.tsx +46 -0
- package/src/components/shared/SearchableInput.tsx +153 -0
- package/src/components/shared/SearchablePopover.tsx +166 -0
- package/src/components/shared/SmartImage.tsx +311 -0
- package/src/components/shared/TruncatedValue.tsx +27 -0
- package/src/components/shared/UnsavedChangesGuard.tsx +138 -0
- package/src/components/shared/index.ts +15 -0
- package/src/components/skeletons/CardSkeleton.tsx +26 -0
- package/src/components/skeletons/CartItemSkeleton.tsx +26 -0
- package/src/components/skeletons/OrderItemSkeleton.tsx +26 -0
- package/src/components/skeletons/ProductCardSkeleton.tsx +26 -0
- package/src/components/skeletons/TableSkeleton.tsx +42 -0
- package/src/components/skeletons/index.ts +5 -0
- package/src/config/index.ts +22 -4
- package/src/config/server.ts +45 -0
- package/src/context/sdk-context.tsx +5 -2
- package/src/generated/auth-openapi.ts +2935 -0
- package/src/generated/cart-openapi.ts +652 -0
- package/src/generated/cms-openapi.ts +1668 -0
- package/src/generated/crm-openapi.ts +986 -0
- package/src/generated/graphql.ts +126 -0
- package/src/generated/index.ts +11 -0
- package/src/generated/media-openapi.ts +613 -0
- package/src/generated/notification-openapi.ts +862 -0
- package/src/generated/payment-openapi.ts +2713 -0
- package/src/generated/store-openapi.ts +9510 -0
- package/src/graphql/operations/collections.graphql +32 -0
- package/src/graphql/operations/products.graphql +197 -0
- package/src/graphql/operations/wishlist.graphql +25 -0
- package/src/hooks/index.ts +9 -2
- package/src/hooks/use-auth.ts +163 -69
- package/src/hooks/use-bespoke-availability.ts +39 -22
- package/src/hooks/use-business-config.ts +51 -0
- package/src/hooks/use-cart.ts +310 -238
- package/src/hooks/use-cms.ts +107 -127
- package/src/hooks/use-collections.ts +35 -55
- package/src/hooks/use-consultations.ts +9 -136
- package/src/hooks/use-countries.ts +19 -28
- package/src/hooks/use-currencies.ts +8 -20
- package/src/hooks/use-customer-address.ts +64 -0
- package/src/hooks/use-feedback.ts +34 -0
- package/src/hooks/use-languages.ts +26 -39
- package/src/hooks/use-media-upload.ts +23 -0
- package/src/hooks/use-notification-stream.ts +88 -0
- package/src/hooks/use-notifications.ts +163 -0
- package/src/hooks/use-order-stream.ts +29 -29
- package/src/hooks/use-orders.ts +91 -73
- package/src/hooks/use-payment.ts +116 -84
- package/src/hooks/use-products.ts +65 -269
- package/src/hooks/use-push-notifications.ts +122 -0
- package/src/hooks/use-reviews.ts +175 -0
- package/src/hooks/use-service-booking.ts +109 -132
- package/src/hooks/use-states.ts +16 -30
- package/src/hooks/use-wishlist.ts +44 -69
- package/src/index.ts +15 -11
- package/src/services/auth.service.ts +422 -132
- package/src/services/bespoke.service.ts +69 -10
- package/src/services/cart.service.ts +155 -186
- package/src/services/cms.service.ts +41 -50
- package/src/services/collection.service.ts +25 -63
- package/src/services/config.service.ts +10 -21
- package/src/services/consultation.service.ts +36 -93
- package/src/services/feedback.service.ts +76 -0
- package/src/services/index.ts +7 -1
- package/src/services/legal.service.ts +62 -0
- package/src/services/logistics.service.ts +235 -53
- package/src/services/media.service.ts +115 -0
- package/src/services/notifications.service.ts +204 -20
- package/src/services/order.service.ts +498 -127
- package/src/services/payment.service.ts +149 -100
- package/src/services/product.service.ts +157 -814
- package/src/services/review.service.ts +211 -0
- package/src/services/service-booking.service.ts +198 -99
- package/src/services/wishlist.service.ts +27 -74
- package/src/types/auth/index.ts +161 -18
- package/src/types/business/index.ts +9 -1
- package/src/types/cms/index.ts +91 -6
- package/src/types/cms/legal.ts +18 -0
- package/src/types/consultations/index.ts +60 -68
- package/src/types/index.ts +5 -3
- package/src/types/media/index.ts +35 -3
- package/src/types/payments/index.ts +80 -16
- package/src/types/reviews/index.ts +116 -0
- package/src/types/services/index.ts +113 -38
- package/src/types/store/checkout.ts +53 -0
- package/src/types/store/index.ts +273 -52
- package/src/types/store/invoice.ts +31 -0
- package/src/types/store/shipping.ts +84 -3
- package/src/types/ui/index.ts +1 -1
- package/src/utils/checkout.ts +19 -3
- package/src/utils/cookieConsent.ts +34 -0
- package/src/utils/format.ts +408 -0
- package/src/utils/geolocation.ts +134 -0
- package/src/utils/graphql-url.ts +47 -0
- package/src/utils/guest-session.ts +59 -0
- package/src/utils/http-client.ts +147 -0
- package/src/utils/index.ts +11 -4
- package/src/utils/status-colors.ts +79 -0
- package/src/utils/token-refresh.ts +115 -0
- package/src/utils/validators.ts +13 -0
- package/src/utils/youtube.ts +18 -0
- package/dist/index-C5YO9gTl.d.mts +0 -507
- package/dist/index-C5YO9gTl.d.ts +0 -507
- package/dist/index-CefG7_xq.d.mts +0 -793
- package/dist/index-DeHCf3zI.d.ts +0 -793
- package/src/graphql/collections.graphql.ts +0 -48
- package/src/graphql/index.ts +0 -7
- package/src/graphql/products.graphql.ts +0 -860
- package/src/graphql/wishlist.graphql.ts +0 -34
- package/src/hooks/use-consultation-stream.ts +0 -142
- package/src/types/bespoke/index.ts +0 -39
|
@@ -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
|
-
|
|
31
|
-
`${config.backendUrls.cms}/sdk/${
|
|
46
|
+
return apiRequest<CmsEntryListResponse>(
|
|
47
|
+
`${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries?${queryParams.toString()}`,
|
|
32
48
|
{
|
|
33
|
-
headers:
|
|
34
|
-
|
|
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
|
-
|
|
54
|
-
`${config.backendUrls.cms}/sdk/${
|
|
65
|
+
return apiRequest<CmsEntry>(
|
|
66
|
+
`${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries/${entryId}`,
|
|
55
67
|
{
|
|
56
|
-
headers:
|
|
57
|
-
|
|
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
|
-
|
|
78
|
-
`${config.backendUrls.cms}/sdk/${
|
|
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
|
-
|
|
107
|
-
`${config.backendUrls.cms}/sdk/${
|
|
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
|
-
|
|
135
|
-
`${config.backendUrls.cms}/sdk/${
|
|
130
|
+
await apiRequest(
|
|
131
|
+
`${config.backendUrls.cms}/sdk/${businessUuid}/${modelSlug}/entries/${entryId}`,
|
|
136
132
|
{
|
|
137
133
|
method: 'DELETE',
|
|
138
|
-
headers:
|
|
139
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
36
|
+
const data = await client.request(GetStorefrontCollectionsDocument, {
|
|
77
37
|
page: params?.page || 1,
|
|
78
38
|
pageSize: params?.pageSize || 20,
|
|
79
|
-
}
|
|
80
|
-
|
|
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(
|
|
93
|
-
|
|
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
|
-
|
|
17
|
-
headers:
|
|
18
|
-
|
|
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
|
-
|
|
32
|
-
headers:
|
|
33
|
-
|
|
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
|
-
|
|
49
|
-
headers:
|
|
50
|
-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
83
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
+
}
|
package/src/services/index.ts
CHANGED
|
@@ -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
|
+
}
|