@cartbase/storefront 0.16.0 → 0.18.0
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/LICENSE +21 -21
- package/package.json +258 -255
- package/src/api/auth.ts +108 -108
- package/src/api/carts.ts +523 -523
- package/src/api/categories.ts +184 -184
- package/src/api/checkout.ts +526 -526
- package/src/api/collections.ts +130 -130
- package/src/api/consent.ts +75 -75
- package/src/api/content.ts +125 -125
- package/src/api/customers.ts +303 -303
- package/src/api/gift-cards.ts +112 -112
- package/src/api/http.ts +180 -130
- package/src/api/index.ts +30 -30
- package/src/api/menus.ts +77 -77
- package/src/api/metaobjects.ts +136 -136
- package/src/api/orders.ts +290 -290
- package/src/api/products.ts +43 -1
- package/src/api/redirects.ts +37 -37
- package/src/api/regions.ts +200 -200
- package/src/api/reviews.ts +259 -259
- package/src/api/search.ts +163 -163
- package/src/api/store.ts +35 -35
- package/src/api/types.ts +91 -91
- package/src/cart-drawer/context.tsx +277 -164
- package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
- package/src/cart-drawer/free-gift.tsx +1 -1
- package/src/cart-drawer/gift-wrap.tsx +82 -82
- package/src/cart-drawer/index.ts +71 -68
- package/src/cart-drawer/item/index.tsx +162 -162
- package/src/cart-drawer/item/quantity.tsx +8 -14
- package/src/cart-drawer/item/upsell.tsx +110 -110
- package/src/cart-drawer/labels.ts +123 -123
- package/src/cart-drawer/mutation-queue.ts +78 -0
- package/src/cart-drawer/notes.tsx +131 -131
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/cart-drawer/summary-breakdown.tsx +197 -197
- package/src/checkout/address-error-copy.ts +117 -117
- package/src/checkout/boxnow-locker-selector.tsx +410 -410
- package/src/checkout/compare-addresses.ts +40 -40
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -63
- package/src/checkout/geocode.ts +154 -154
- package/src/checkout/gift-card-section.tsx +224 -224
- package/src/checkout/index.ts +72 -72
- package/src/checkout/labels.ts +495 -495
- package/src/checkout/payment-button.tsx +372 -372
- package/src/checkout/payment-error-copy.ts +154 -154
- package/src/checkout/promotion-error-copy.ts +91 -91
- package/src/checkout/use-checkout-orchestration.ts +1592 -1592
- package/src/common/country-flag.tsx +52 -52
- package/src/common/country-select.tsx +11 -11
- package/src/common/index.ts +20 -20
- package/src/common/market-select.tsx +57 -57
- package/src/index.ts +12 -12
- package/src/lib/country-name.ts +59 -59
- package/src/lib/get-product-price.ts +133 -133
- package/src/lib/media-image.tsx +39 -0
- package/src/lib/payment-constants.ts +53 -53
- package/src/lib/platform.ts +13 -13
- package/src/lib/price.tsx +39 -39
- package/src/lib/store-api-error.ts +36 -36
- package/src/lib/variant-caption.ts +32 -32
- package/src/locales/bg.ts +467 -467
- package/src/locales/context.ts +37 -37
- package/src/locales/en.ts +26 -26
- package/src/locales/es.ts +466 -466
- package/src/locales/index.ts +19 -19
- package/src/locales/provider.tsx +59 -59
- package/src/locales/types.ts +77 -77
- package/src/order/index.ts +62 -62
- package/src/order/labels.ts +79 -79
- package/src/order/order-item.tsx +1 -1
- package/src/order/order-totals.tsx +250 -250
- package/src/primitives/select-field.tsx +93 -93
- package/src/products/image-gallery.tsx +43 -43
- package/src/products/index.ts +68 -59
- package/src/products/option-select.tsx +67 -67
- package/src/products/product-actions-wrapper.tsx +63 -62
- package/src/products/product-actions.tsx +151 -150
- package/src/products/product-price.tsx +69 -69
- package/src/products/product-promises.tsx +61 -61
- package/src/products/product-specs.tsx +65 -65
- package/src/products/product-tabs.tsx +123 -123
- package/src/products/product-template.tsx +163 -162
- package/src/products/purchase-options.tsx +130 -130
- package/src/products/sets.ts +110 -0
- package/src/products/thumbnail.tsx +1 -1
- package/src/products/use-product-actions.ts +199 -164
- package/src/products/variant-matching.ts +71 -71
- package/src/products/variant-url.ts +74 -74
- package/src/reviews-ui/index.ts +73 -73
- package/src/store/category-template.tsx +136 -136
- package/src/store/index.ts +40 -40
- package/src/tracking/chatgpt-pixel.tsx +99 -99
- package/src/tracking/consent-init.tsx +62 -62
- package/src/tracking/events.ts +348 -348
- package/src/tracking/ga4.tsx +93 -93
- package/src/tracking/google-ads.ts +84 -84
- package/src/tracking/gtm.tsx +60 -60
- package/src/tracking/inline-script.ts +49 -49
- package/src/tracking/oaiq.ts +206 -206
- package/src/tracking/tiktok-pixel.tsx +91 -91
- package/src/tracking/track-init.tsx +56 -56
- package/src/tracking/track-order-purchase.tsx +122 -122
- package/src/tracking/ttq.ts +180 -180
- package/src/tracking/use-tracking-config.ts +54 -54
- package/theme/index.css +25 -25
- package/theme/theme.css +150 -150
- package/theme/tokens.css +106 -106
package/src/api/categories.ts
CHANGED
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @cartbase/storefront/api/categories — product categories, tags, types.
|
|
3
|
-
*
|
|
4
|
-
* Taxonomy reads for navigation trees and filter UIs. Categories are
|
|
5
|
-
* hierarchical (`parent_category_id`) with optional ancestor/descendant tree
|
|
6
|
-
* embedding; tags and types are flat value lists. Only ACTIVE, non-internal
|
|
7
|
-
* categories are storefront-visible (RLS-enforced — inactive/internal 404
|
|
8
|
-
* even by id). Docs: docs/storefront/categories.md.
|
|
9
|
-
*/
|
|
10
|
-
import type { StorefrontClient } from "./http"
|
|
11
|
-
import type { IsoDateString, ListEnvelope, PaginationQuery } from "./types"
|
|
12
|
-
|
|
13
|
-
export interface StoreProductCategory {
|
|
14
|
-
id: string
|
|
15
|
-
name: string
|
|
16
|
-
handle: string
|
|
17
|
-
description: string | null
|
|
18
|
-
parent_category_id: string | null
|
|
19
|
-
rank: number
|
|
20
|
-
is_active: true
|
|
21
|
-
is_internal: false
|
|
22
|
-
/** Materialized path (internal ordering detail). */
|
|
23
|
-
mpath: string | null
|
|
24
|
-
/** SEO override; null = fall back to `name`. */
|
|
25
|
-
seo_title: string | null
|
|
26
|
-
/** SEO override; null = fall back to `description`. */
|
|
27
|
-
seo_description: string | null
|
|
28
|
-
metadata: Record<string, unknown> | null
|
|
29
|
-
created_at: IsoDateString
|
|
30
|
-
updated_at: IsoDateString
|
|
31
|
-
/** Present only when `include_descendants_tree` was requested. */
|
|
32
|
-
category_children?: StoreProductCategory[]
|
|
33
|
-
/** Present only when `include_ancestors_tree` was requested. */
|
|
34
|
-
parent_category?: StoreProductCategory | null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface StoreProductTag {
|
|
38
|
-
id: string
|
|
39
|
-
value: string
|
|
40
|
-
metadata: Record<string, unknown> | null
|
|
41
|
-
created_at: IsoDateString
|
|
42
|
-
updated_at: IsoDateString
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface StoreProductType {
|
|
46
|
-
id: string
|
|
47
|
-
value: string
|
|
48
|
-
metadata: Record<string, unknown> | null
|
|
49
|
-
created_at: IsoDateString
|
|
50
|
-
updated_at: IsoDateString
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ListCategoriesQuery extends PaginationQuery {
|
|
54
|
-
/** Case-insensitive substring match on name. */
|
|
55
|
-
q?: string
|
|
56
|
-
handle?: string
|
|
57
|
-
/** List one level: children of this category. */
|
|
58
|
-
parent_category_id?: string
|
|
59
|
-
/** Embed `parent_category` chains on each row. */
|
|
60
|
-
include_ancestors_tree?: boolean
|
|
61
|
-
/** Embed `category_children` recursively on each row. */
|
|
62
|
-
include_descendants_tree?: boolean
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface CategoryListResponse extends ListEnvelope {
|
|
66
|
-
product_categories: StoreProductCategory[]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface CategoryResponse {
|
|
70
|
-
product_category: StoreProductCategory
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface RetrieveCategoryQuery {
|
|
74
|
-
include_ancestors_tree?: boolean
|
|
75
|
-
include_descendants_tree?: boolean
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface ListTagsQuery extends PaginationQuery {
|
|
79
|
-
/** Case-insensitive substring match on value. */
|
|
80
|
-
q?: string
|
|
81
|
-
/** Exact value match. */
|
|
82
|
-
value?: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface TagListResponse extends ListEnvelope {
|
|
86
|
-
product_tags: StoreProductTag[]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface TagResponse {
|
|
90
|
-
product_tag: StoreProductTag
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export type ListTypesQuery = ListTagsQuery
|
|
94
|
-
|
|
95
|
-
export interface TypeListResponse extends ListEnvelope {
|
|
96
|
-
product_types: StoreProductType[]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface TypeResponse {
|
|
100
|
-
product_type: StoreProductType
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* List active, non-internal categories ordered by `rank`. Tree flags embed
|
|
105
|
-
* ancestors/descendants per row (visible categories only).
|
|
106
|
-
*
|
|
107
|
-
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
108
|
-
* 400 `validation_failed`. Settings: category `is_active`/`is_internal`
|
|
109
|
-
* flags and rank ordering (Admin → Categories).
|
|
110
|
-
*/
|
|
111
|
-
export async function listCategories(
|
|
112
|
-
client: StorefrontClient,
|
|
113
|
-
query?: ListCategoriesQuery
|
|
114
|
-
): Promise<CategoryListResponse> {
|
|
115
|
-
return client.get("/api/store/product-categories", { query: { ...query } })
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Retrieve one category by id, optionally with its ancestor/descendant tree.
|
|
120
|
-
*
|
|
121
|
-
* Auth: anon (`x-client-id`). Errors: 404 `not_found` (unknown, deleted, or
|
|
122
|
-
* inactive/internal — RLS hides them even by id).
|
|
123
|
-
*/
|
|
124
|
-
export async function retrieveCategory(
|
|
125
|
-
client: StorefrontClient,
|
|
126
|
-
categoryId: string,
|
|
127
|
-
query?: RetrieveCategoryQuery
|
|
128
|
-
): Promise<CategoryResponse> {
|
|
129
|
-
return client.get(`/api/store/product-categories/${encodeURIComponent(categoryId)}`, {
|
|
130
|
-
query: { ...query },
|
|
131
|
-
})
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* List product tags ordered by value (filter chips; tag ids feed
|
|
136
|
-
* `tag_id` filters on product listings and search).
|
|
137
|
-
*
|
|
138
|
-
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
139
|
-
* 400 `validation_failed`.
|
|
140
|
-
*/
|
|
141
|
-
export async function listProductTags(
|
|
142
|
-
client: StorefrontClient,
|
|
143
|
-
query?: ListTagsQuery
|
|
144
|
-
): Promise<TagListResponse> {
|
|
145
|
-
return client.get("/api/store/product-tags", { query: { ...query } })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Retrieve one product tag by id.
|
|
150
|
-
*
|
|
151
|
-
* Auth: anon (`x-client-id`). Errors: 404 `not_found`.
|
|
152
|
-
*/
|
|
153
|
-
export async function retrieveProductTag(
|
|
154
|
-
client: StorefrontClient,
|
|
155
|
-
tagId: string
|
|
156
|
-
): Promise<TagResponse> {
|
|
157
|
-
return client.get(`/api/store/product-tags/${encodeURIComponent(tagId)}`)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* List product types ordered by value (type ids feed `type_id` filters on
|
|
162
|
-
* product listings and search).
|
|
163
|
-
*
|
|
164
|
-
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
165
|
-
* 400 `validation_failed`.
|
|
166
|
-
*/
|
|
167
|
-
export async function listProductTypes(
|
|
168
|
-
client: StorefrontClient,
|
|
169
|
-
query?: ListTypesQuery
|
|
170
|
-
): Promise<TypeListResponse> {
|
|
171
|
-
return client.get("/api/store/product-types", { query: { ...query } })
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Retrieve one product type by id.
|
|
176
|
-
*
|
|
177
|
-
* Auth: anon (`x-client-id`). Errors: 404 `not_found`.
|
|
178
|
-
*/
|
|
179
|
-
export async function retrieveProductType(
|
|
180
|
-
client: StorefrontClient,
|
|
181
|
-
typeId: string
|
|
182
|
-
): Promise<TypeResponse> {
|
|
183
|
-
return client.get(`/api/store/product-types/${encodeURIComponent(typeId)}`)
|
|
184
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @cartbase/storefront/api/categories — product categories, tags, types.
|
|
3
|
+
*
|
|
4
|
+
* Taxonomy reads for navigation trees and filter UIs. Categories are
|
|
5
|
+
* hierarchical (`parent_category_id`) with optional ancestor/descendant tree
|
|
6
|
+
* embedding; tags and types are flat value lists. Only ACTIVE, non-internal
|
|
7
|
+
* categories are storefront-visible (RLS-enforced — inactive/internal 404
|
|
8
|
+
* even by id). Docs: docs/storefront/categories.md.
|
|
9
|
+
*/
|
|
10
|
+
import type { StorefrontClient } from "./http"
|
|
11
|
+
import type { IsoDateString, ListEnvelope, PaginationQuery } from "./types"
|
|
12
|
+
|
|
13
|
+
export interface StoreProductCategory {
|
|
14
|
+
id: string
|
|
15
|
+
name: string
|
|
16
|
+
handle: string
|
|
17
|
+
description: string | null
|
|
18
|
+
parent_category_id: string | null
|
|
19
|
+
rank: number
|
|
20
|
+
is_active: true
|
|
21
|
+
is_internal: false
|
|
22
|
+
/** Materialized path (internal ordering detail). */
|
|
23
|
+
mpath: string | null
|
|
24
|
+
/** SEO override; null = fall back to `name`. */
|
|
25
|
+
seo_title: string | null
|
|
26
|
+
/** SEO override; null = fall back to `description`. */
|
|
27
|
+
seo_description: string | null
|
|
28
|
+
metadata: Record<string, unknown> | null
|
|
29
|
+
created_at: IsoDateString
|
|
30
|
+
updated_at: IsoDateString
|
|
31
|
+
/** Present only when `include_descendants_tree` was requested. */
|
|
32
|
+
category_children?: StoreProductCategory[]
|
|
33
|
+
/** Present only when `include_ancestors_tree` was requested. */
|
|
34
|
+
parent_category?: StoreProductCategory | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StoreProductTag {
|
|
38
|
+
id: string
|
|
39
|
+
value: string
|
|
40
|
+
metadata: Record<string, unknown> | null
|
|
41
|
+
created_at: IsoDateString
|
|
42
|
+
updated_at: IsoDateString
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface StoreProductType {
|
|
46
|
+
id: string
|
|
47
|
+
value: string
|
|
48
|
+
metadata: Record<string, unknown> | null
|
|
49
|
+
created_at: IsoDateString
|
|
50
|
+
updated_at: IsoDateString
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ListCategoriesQuery extends PaginationQuery {
|
|
54
|
+
/** Case-insensitive substring match on name. */
|
|
55
|
+
q?: string
|
|
56
|
+
handle?: string
|
|
57
|
+
/** List one level: children of this category. */
|
|
58
|
+
parent_category_id?: string
|
|
59
|
+
/** Embed `parent_category` chains on each row. */
|
|
60
|
+
include_ancestors_tree?: boolean
|
|
61
|
+
/** Embed `category_children` recursively on each row. */
|
|
62
|
+
include_descendants_tree?: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CategoryListResponse extends ListEnvelope {
|
|
66
|
+
product_categories: StoreProductCategory[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CategoryResponse {
|
|
70
|
+
product_category: StoreProductCategory
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RetrieveCategoryQuery {
|
|
74
|
+
include_ancestors_tree?: boolean
|
|
75
|
+
include_descendants_tree?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ListTagsQuery extends PaginationQuery {
|
|
79
|
+
/** Case-insensitive substring match on value. */
|
|
80
|
+
q?: string
|
|
81
|
+
/** Exact value match. */
|
|
82
|
+
value?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TagListResponse extends ListEnvelope {
|
|
86
|
+
product_tags: StoreProductTag[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TagResponse {
|
|
90
|
+
product_tag: StoreProductTag
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ListTypesQuery = ListTagsQuery
|
|
94
|
+
|
|
95
|
+
export interface TypeListResponse extends ListEnvelope {
|
|
96
|
+
product_types: StoreProductType[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface TypeResponse {
|
|
100
|
+
product_type: StoreProductType
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* List active, non-internal categories ordered by `rank`. Tree flags embed
|
|
105
|
+
* ancestors/descendants per row (visible categories only).
|
|
106
|
+
*
|
|
107
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
108
|
+
* 400 `validation_failed`. Settings: category `is_active`/`is_internal`
|
|
109
|
+
* flags and rank ordering (Admin → Categories).
|
|
110
|
+
*/
|
|
111
|
+
export async function listCategories(
|
|
112
|
+
client: StorefrontClient,
|
|
113
|
+
query?: ListCategoriesQuery
|
|
114
|
+
): Promise<CategoryListResponse> {
|
|
115
|
+
return client.get("/api/store/product-categories", { query: { ...query } })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Retrieve one category by id, optionally with its ancestor/descendant tree.
|
|
120
|
+
*
|
|
121
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found` (unknown, deleted, or
|
|
122
|
+
* inactive/internal — RLS hides them even by id).
|
|
123
|
+
*/
|
|
124
|
+
export async function retrieveCategory(
|
|
125
|
+
client: StorefrontClient,
|
|
126
|
+
categoryId: string,
|
|
127
|
+
query?: RetrieveCategoryQuery
|
|
128
|
+
): Promise<CategoryResponse> {
|
|
129
|
+
return client.get(`/api/store/product-categories/${encodeURIComponent(categoryId)}`, {
|
|
130
|
+
query: { ...query },
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* List product tags ordered by value (filter chips; tag ids feed
|
|
136
|
+
* `tag_id` filters on product listings and search).
|
|
137
|
+
*
|
|
138
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
139
|
+
* 400 `validation_failed`.
|
|
140
|
+
*/
|
|
141
|
+
export async function listProductTags(
|
|
142
|
+
client: StorefrontClient,
|
|
143
|
+
query?: ListTagsQuery
|
|
144
|
+
): Promise<TagListResponse> {
|
|
145
|
+
return client.get("/api/store/product-tags", { query: { ...query } })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Retrieve one product tag by id.
|
|
150
|
+
*
|
|
151
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found`.
|
|
152
|
+
*/
|
|
153
|
+
export async function retrieveProductTag(
|
|
154
|
+
client: StorefrontClient,
|
|
155
|
+
tagId: string
|
|
156
|
+
): Promise<TagResponse> {
|
|
157
|
+
return client.get(`/api/store/product-tags/${encodeURIComponent(tagId)}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* List product types ordered by value (type ids feed `type_id` filters on
|
|
162
|
+
* product listings and search).
|
|
163
|
+
*
|
|
164
|
+
* Auth: anon (`x-client-id`). Errors: 400 `missing_client_id`,
|
|
165
|
+
* 400 `validation_failed`.
|
|
166
|
+
*/
|
|
167
|
+
export async function listProductTypes(
|
|
168
|
+
client: StorefrontClient,
|
|
169
|
+
query?: ListTypesQuery
|
|
170
|
+
): Promise<TypeListResponse> {
|
|
171
|
+
return client.get("/api/store/product-types", { query: { ...query } })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Retrieve one product type by id.
|
|
176
|
+
*
|
|
177
|
+
* Auth: anon (`x-client-id`). Errors: 404 `not_found`.
|
|
178
|
+
*/
|
|
179
|
+
export async function retrieveProductType(
|
|
180
|
+
client: StorefrontClient,
|
|
181
|
+
typeId: string
|
|
182
|
+
): Promise<TypeResponse> {
|
|
183
|
+
return client.get(`/api/store/product-types/${encodeURIComponent(typeId)}`)
|
|
184
|
+
}
|