@teleporthq/teleport-plugin-next-data-source 0.43.66 → 0.43.68
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/__tests__/array-mapper-cache-wiring.test.ts +11 -0
- package/__tests__/array-overlap-sql.test.ts +53 -0
- package/__tests__/blog-post-page-enrichment.test.ts +214 -0
- package/__tests__/browser-row-policy.test.ts +503 -0
- package/__tests__/data-source-cache-wrapping.test.ts +18 -2
- package/__tests__/data-source-request-locale.test.ts +313 -0
- package/__tests__/ecommerce-product-options.test.ts +206 -0
- package/__tests__/ecommerce-product-pages.test.ts +82 -0
- package/__tests__/ecommerce-product-pre-options.golden.json +505 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +293 -0
- package/__tests__/ecommerce-product-subscriptions.test.ts +121 -0
- package/__tests__/fetchers.test.ts +2 -2
- package/__tests__/gated-data-provider.test.ts +88 -0
- package/__tests__/items-path.test.ts +150 -0
- package/__tests__/localized-columns.test.ts +217 -0
- package/__tests__/numbered-pagination.test.ts +15 -0
- package/__tests__/product-filter-fields.ts +230 -0
- package/__tests__/product-filter-sql.fixture.json +7 -0
- package/__tests__/product-options-storefront.fixture.json +2510 -0
- package/__tests__/product-page-url-fixtures.json +25 -0
- package/__tests__/product-price-sort.ts +4 -2
- package/__tests__/raw-rows-mode.test.ts +111 -0
- package/__tests__/refetch-loading-state.test.ts +23 -0
- package/__tests__/rest-api-response-cache.test.ts +434 -0
- package/__tests__/search-clear-button.test.ts +122 -0
- package/__tests__/table-read-route-protected-tables.test.ts +493 -0
- package/dist/cjs/array-overlap-sql.d.ts +21 -0
- package/dist/cjs/array-overlap-sql.d.ts.map +1 -0
- package/dist/cjs/array-overlap-sql.js +25 -0
- package/dist/cjs/array-overlap-sql.js.map +1 -0
- package/dist/cjs/content-localization.d.ts +37 -0
- package/dist/cjs/content-localization.d.ts.map +1 -0
- package/dist/cjs/content-localization.js +29 -0
- package/dist/cjs/content-localization.js.map +1 -0
- package/dist/cjs/count-effect.d.ts +11 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +30 -2
- package/dist/cjs/count-effect.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -4
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +1 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +9 -4
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts +8 -1
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
- package/dist/cjs/fetchers/raw-query.js +42 -4
- package/dist/cjs/fetchers/raw-query.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +1 -1
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +7 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +16 -6
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js +66 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js +12 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js +64 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js +34 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +60 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/items-path.d.ts +10 -0
- package/dist/cjs/items-path.d.ts.map +1 -0
- package/dist/cjs/items-path.js +86 -0
- package/dist/cjs/items-path.js.map +1 -0
- package/dist/cjs/loading-state.d.ts +16 -1
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +69 -3
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/localized-columns.d.ts +56 -0
- package/dist/cjs/localized-columns.d.ts.map +1 -0
- package/dist/cjs/localized-columns.js +137 -0
- package/dist/cjs/localized-columns.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +140 -50
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-filter-fields.d.ts +87 -0
- package/dist/cjs/product-filter-fields.d.ts.map +1 -0
- package/dist/cjs/product-filter-fields.js +161 -0
- package/dist/cjs/product-filter-fields.js.map +1 -0
- package/dist/cjs/request-locale.d.ts +49 -0
- package/dist/cjs/request-locale.d.ts.map +1 -0
- package/dist/cjs/request-locale.js +130 -0
- package/dist/cjs/request-locale.js.map +1 -0
- package/dist/cjs/search-clear.d.ts +31 -0
- package/dist/cjs/search-clear.d.ts.map +1 -0
- package/dist/cjs/search-clear.js +103 -0
- package/dist/cjs/search-clear.js.map +1 -0
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js +25 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/cjs/transformations/blog-comments.d.ts +30 -0
- package/dist/cjs/transformations/blog-comments.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-comments.js +34 -0
- package/dist/cjs/transformations/blog-comments.js.map +1 -0
- package/dist/cjs/transformations/blog-post.d.ts +2 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -1
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts +9 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +28 -1
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/heading-anchors.d.ts +25 -0
- package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/cjs/transformations/heading-anchors.js +29 -0
- package/dist/cjs/transformations/heading-anchors.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +61 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +69 -19
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
- package/dist/cjs/transformations/shared-utils.js +1 -1
- package/dist/cjs/transformations/shared-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +11 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +60 -16
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/array-overlap-sql.d.ts +21 -0
- package/dist/esm/array-overlap-sql.d.ts.map +1 -0
- package/dist/esm/array-overlap-sql.js +21 -0
- package/dist/esm/array-overlap-sql.js.map +1 -0
- package/dist/esm/content-localization.d.ts +37 -0
- package/dist/esm/content-localization.d.ts.map +1 -0
- package/dist/esm/content-localization.js +24 -0
- package/dist/esm/content-localization.js.map +1 -0
- package/dist/esm/count-effect.d.ts +11 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +28 -1
- package/dist/esm/count-effect.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +11 -5
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +1 -1
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +1 -1
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +1 -1
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +1 -1
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +1 -1
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +1 -1
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +1 -1
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +9 -4
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts +8 -1
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
- package/dist/esm/fetchers/raw-query.js +42 -4
- package/dist/esm/fetchers/raw-query.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +1 -1
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +7 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +16 -6
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +1 -1
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js +60 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js +59 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.js +29 -0
- package/dist/esm/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +55 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/items-path.d.ts +10 -0
- package/dist/esm/items-path.d.ts.map +1 -0
- package/dist/esm/items-path.js +58 -0
- package/dist/esm/items-path.js.map +1 -0
- package/dist/esm/loading-state.d.ts +16 -1
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +69 -3
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/localized-columns.d.ts +56 -0
- package/dist/esm/localized-columns.d.ts.map +1 -0
- package/dist/esm/localized-columns.js +132 -0
- package/dist/esm/localized-columns.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +141 -51
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-filter-fields.d.ts +87 -0
- package/dist/esm/product-filter-fields.d.ts.map +1 -0
- package/dist/esm/product-filter-fields.js +154 -0
- package/dist/esm/product-filter-fields.js.map +1 -0
- package/dist/esm/request-locale.d.ts +49 -0
- package/dist/esm/request-locale.d.ts.map +1 -0
- package/dist/esm/request-locale.js +98 -0
- package/dist/esm/request-locale.js.map +1 -0
- package/dist/esm/search-clear.d.ts +31 -0
- package/dist/esm/search-clear.d.ts.map +1 -0
- package/dist/esm/search-clear.js +74 -0
- package/dist/esm/search-clear.js.map +1 -0
- package/dist/esm/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/esm/transformations/blog-adjacent-posts.js +21 -0
- package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/esm/transformations/blog-comments.d.ts +30 -0
- package/dist/esm/transformations/blog-comments.d.ts.map +1 -0
- package/dist/esm/transformations/blog-comments.js +30 -0
- package/dist/esm/transformations/blog-comments.js.map +1 -0
- package/dist/esm/transformations/blog-post.d.ts +2 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -1
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/ecommerce-product.d.ts +9 -1
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +29 -2
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/heading-anchors.d.ts +25 -0
- package/dist/esm/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/esm/transformations/heading-anchors.js +25 -0
- package/dist/esm/transformations/heading-anchors.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +61 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +69 -19
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
- package/dist/esm/transformations/shared-utils.js +1 -1
- package/dist/esm/transformations/shared-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +11 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +60 -16
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/array-overlap-sql.ts +41 -0
- package/src/content-localization.ts +53 -0
- package/src/count-effect.ts +53 -2
- package/src/data-source-fetchers.ts +41 -5
- package/src/fetchers/airtable.ts +4 -2
- package/src/fetchers/clickhouse.ts +4 -2
- package/src/fetchers/firestore.ts +4 -2
- package/src/fetchers/javascript.ts +9 -1
- package/src/fetchers/mariadb.ts +4 -2
- package/src/fetchers/mongodb.ts +4 -2
- package/src/fetchers/mysql.ts +4 -2
- package/src/fetchers/postgresql.ts +53 -12
- package/src/fetchers/raw-query.ts +60 -5
- package/src/fetchers/redshift.ts +4 -2
- package/src/fetchers/rest-api.ts +27 -5
- package/src/fetchers/teleport.ts +85 -22
- package/src/fetchers/turso.ts +4 -2
- package/src/fetchers/utils/browser-row-policy.ts +212 -0
- package/src/fetchers/utils/request-sql-guards.ts +25 -0
- package/src/fetchers/utils/rest-api-response-cache.ts +188 -0
- package/src/fetchers/utils/table-access-guard.ts +39 -0
- package/src/index.ts +79 -11
- package/src/items-path.ts +74 -0
- package/src/loading-state.ts +134 -3
- package/src/localized-columns.ts +146 -0
- package/src/pagination-plugin.ts +206 -60
- package/src/product-filter-fields.ts +173 -0
- package/src/request-locale.ts +113 -0
- package/src/search-clear.ts +102 -0
- package/src/transformations/blog-adjacent-posts.ts +74 -0
- package/src/transformations/blog-comments.ts +132 -0
- package/src/transformations/blog-post.ts +38 -5
- package/src/transformations/ecommerce-product.ts +568 -5
- package/src/transformations/heading-anchors.ts +85 -0
- package/src/transformations/index.ts +191 -23
- package/src/transformations/shared-utils.ts +6 -0
- package/src/utils.ts +109 -19
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import type { UIDLEcommerceCategory } from '@teleporthq/teleport-types'
|
|
2
|
-
import {
|
|
1
|
+
import type { UIDLEcommerceCategory, UIDLEcommerceProductPages } from '@teleporthq/teleport-types'
|
|
2
|
+
import {
|
|
3
|
+
ProductDiscounts,
|
|
4
|
+
ProductOptions,
|
|
5
|
+
ProductRatings,
|
|
6
|
+
StorefrontTax,
|
|
7
|
+
} from '@teleporthq/teleport-shared'
|
|
3
8
|
import { generateCategoryTaxonomyCode } from './category-taxonomy'
|
|
4
9
|
|
|
5
10
|
/**
|
|
@@ -27,6 +32,67 @@ export interface EcommerceProductTransformOptions {
|
|
|
27
32
|
* `apps/gui/app/project-page/features/e-commerce/utils/product-variants.ts`.
|
|
28
33
|
*/
|
|
29
34
|
allowBackorders?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* Where the standard product page and every custom product page live
|
|
37
|
+
* (`ecommerceSettings.productPages`). Present only when the store has a
|
|
38
|
+
* custom product page; then every product carries `productPageKey` (the page
|
|
39
|
+
* it opens on, '' = the standard one) and `productPageUrl` (its canonical
|
|
40
|
+
* locale-less address). Absent, the transform is exactly what it was.
|
|
41
|
+
*/
|
|
42
|
+
productPages?: UIDLEcommerceProductPages
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The product-page helpers baked into the transform. TWIN of the GUI's
|
|
47
|
+
* `features/e-commerce/utils/product-pages/product-pages-config.ts`
|
|
48
|
+
* (`normalizeProductPageKey` + `buildProductPagePath`) — both pinned by the
|
|
49
|
+
* shared `product-page-url-fixtures.json`. Only routes and attributes are baked:
|
|
50
|
+
* the page names never reach the site.
|
|
51
|
+
*/
|
|
52
|
+
const generateProductPagesCode = (productPages: UIDLEcommerceProductPages): string => {
|
|
53
|
+
const standard = productPages.standard
|
|
54
|
+
const baked = {
|
|
55
|
+
standard:
|
|
56
|
+
standard && typeof standard.route === 'string' && typeof standard.attribute === 'string'
|
|
57
|
+
? { route: standard.route, attribute: standard.attribute }
|
|
58
|
+
: null,
|
|
59
|
+
pages: Object.keys(productPages.pages || {}).reduce(
|
|
60
|
+
(acc: Record<string, { route: string; attribute: string }>, key) => {
|
|
61
|
+
const page = productPages.pages[key]
|
|
62
|
+
if (page && typeof page.route === 'string' && typeof page.attribute === 'string') {
|
|
63
|
+
acc[key] = { route: page.route, attribute: page.attribute }
|
|
64
|
+
}
|
|
65
|
+
return acc
|
|
66
|
+
},
|
|
67
|
+
{}
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
return `
|
|
71
|
+
// Where every product page lives, baked from the store's custom product pages.
|
|
72
|
+
// A product opens on the custom page its product_page column names while that
|
|
73
|
+
// page exists, on the standard product page otherwise. Regenerating the project
|
|
74
|
+
// refreshes it — do not hand-edit.
|
|
75
|
+
var PRODUCT_PAGES = ${JSON.stringify(baked)}
|
|
76
|
+
|
|
77
|
+
function resolveProductPageKey(raw) {
|
|
78
|
+
var key = typeof raw === 'string' ? raw.trim() : ''
|
|
79
|
+
return key !== '' && Object.prototype.hasOwnProperty.call(PRODUCT_PAGES.pages, key) ? key : ''
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A product's canonical address: its page's static base, then the value of the
|
|
83
|
+
// column the page's address ends with, encoded. '' when either is missing.
|
|
84
|
+
// values holds the slug in the page's language and the id; any other column
|
|
85
|
+
// the address ends with (a SKU) is read from the row.
|
|
86
|
+
function buildProductPageUrl(key, values, record) {
|
|
87
|
+
var page = key !== '' ? PRODUCT_PAGES.pages[key] : PRODUCT_PAGES.standard
|
|
88
|
+
if (!page || !page.route) return ''
|
|
89
|
+
var value = values[page.attribute]
|
|
90
|
+
if ((value === null || value === undefined) && record) value = record[page.attribute]
|
|
91
|
+
if (value === null || value === undefined || String(value) === '') return ''
|
|
92
|
+
var base = page.route === '/' ? '' : page.route
|
|
93
|
+
return base + '/' + encodeURIComponent(String(value))
|
|
94
|
+
}
|
|
95
|
+
`
|
|
30
96
|
}
|
|
31
97
|
|
|
32
98
|
/**
|
|
@@ -45,13 +111,14 @@ export const generateEcommerceProductTransformationCode = (
|
|
|
45
111
|
return `
|
|
46
112
|
${taxHelperCode}
|
|
47
113
|
${ProductDiscounts.generateProductDiscountHelperCode()}
|
|
114
|
+
${ProductOptions.generateProductOptionsHelperCode()}
|
|
48
115
|
${ProductRatings.generateProductRatingHelperCode()}
|
|
49
116
|
|
|
50
117
|
// Baked from the merchant's stock settings: TRUE when stock never blocks a
|
|
51
118
|
// purchase (stock management off OR backorders allowed). Regenerating the
|
|
52
119
|
// project refreshes it — do not hand-edit. See EcommerceProductTransformOptions.
|
|
53
120
|
var ALLOW_BACKORDERS = ${allowBackordersLiteral}
|
|
54
|
-
|
|
121
|
+
${options.productPages ? generateProductPagesCode(options.productPages) : ''}
|
|
55
122
|
${generateCategoryTaxonomyCode('PRODUCT_CATEGORIES_BY_ID', options.categories)}
|
|
56
123
|
// The merchant's "Show 3D model by default" toggle, as every backend may hand
|
|
57
124
|
// it back: a real boolean, 1/0, 'true'/'false'/'t'/'f' strings — and NULL /
|
|
@@ -69,6 +136,57 @@ function normalizeShowModelFlag(raw) {
|
|
|
69
136
|
return true
|
|
70
137
|
}
|
|
71
138
|
|
|
139
|
+
// A BOOLEAN column as every backend may hand it back; NULL / absent reads as
|
|
140
|
+
// off. MUST mirror parseBoolean in the GUI's features/e-commerce/utils/parse-boolean.ts.
|
|
141
|
+
function readBooleanFlag(raw) {
|
|
142
|
+
if (typeof raw === 'boolean') return raw
|
|
143
|
+
if (typeof raw === 'number') return raw !== 0
|
|
144
|
+
if (typeof raw === 'string') {
|
|
145
|
+
var token = raw.trim().toLowerCase()
|
|
146
|
+
return token === 'true' || token === 't' || token === '1' || token === 'yes'
|
|
147
|
+
}
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// "month", "3 months" — the period a recurring price is billed per. The
|
|
152
|
+
// interval is read however the driver spelled it (anything unknown is a
|
|
153
|
+
// month) and the count is a positive whole number (anything else is 1). MUST
|
|
154
|
+
// mirror formatBillingPeriod in the GUI's features/e-commerce/constants/subscriptions.ts.
|
|
155
|
+
var BILLING_INTERVALS = ['day', 'week', 'month', 'year']
|
|
156
|
+
function formatBillingPeriodLabel(interval, intervalCount) {
|
|
157
|
+
var unit = String(interval == null ? '' : interval).trim().toLowerCase()
|
|
158
|
+
if (BILLING_INTERVALS.indexOf(unit) === -1) unit = 'month'
|
|
159
|
+
var count = normalizeBillingIntervalCountValue(intervalCount)
|
|
160
|
+
return count === 1 ? unit : count + ' ' + unit + 's'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A positive whole number of intervals; anything else is 1. MUST mirror
|
|
164
|
+
// normalizeBillingIntervalCount in the GUI's features/e-commerce/constants/subscriptions.ts.
|
|
165
|
+
function normalizeBillingIntervalCountValue(intervalCount) {
|
|
166
|
+
var count = Math.floor(Number(intervalCount))
|
|
167
|
+
return isFinite(count) && count >= 1 ? count : 1
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// A positive whole number read off an INTEGER column, or 0 for NULL, zero, a
|
|
171
|
+
// negative or anything unreadable. MUST mirror resolvePositiveCount in the
|
|
172
|
+
// GUI's features/e-commerce/utils/digital-products/digital-product.ts (which
|
|
173
|
+
// answers null where this answers 0).
|
|
174
|
+
function resolvePositiveCountValue(raw) {
|
|
175
|
+
if (raw === null || raw === undefined || raw === '') return 0
|
|
176
|
+
var parsed = Math.floor(Number(raw))
|
|
177
|
+
return isFinite(parsed) && parsed > 0 ? parsed : 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// "Billed every month", "Billed every 3 months · 14-day free trial" — the
|
|
181
|
+
// purchase-details line of a recurring product. MUST mirror
|
|
182
|
+
// formatBillingSummary in the GUI's
|
|
183
|
+
// features/e-commerce/utils/subscriptions/billing-summary.ts.
|
|
184
|
+
function formatBillingSummaryLabel(interval, intervalCount, trialDays) {
|
|
185
|
+
var summary = 'Billed every ' + formatBillingPeriodLabel(interval, intervalCount)
|
|
186
|
+
var trial = resolvePositiveCountValue(trialDays)
|
|
187
|
+
return trial === 0 ? summary : summary + ' · ' + trial + '-day free trial'
|
|
188
|
+
}
|
|
189
|
+
|
|
72
190
|
// The product's media gallery in display order, HERO FIRST: the model when it
|
|
73
191
|
// is shown by default, otherwise the images with the model last. Each entry is
|
|
74
192
|
// { kind: 'image' | 'model', src, poster, thumbnail, alt }; 'thumbnail' is what
|
|
@@ -112,7 +230,12 @@ function buildEcommerceProduct(record, options) {
|
|
|
112
230
|
|
|
113
231
|
// i18n-resolved text fields
|
|
114
232
|
var name = resolveI18nField(record, 'name', 'name', currentLang, mainLang) || ''
|
|
115
|
-
var slug = resolveI18nField(record, 'slug', 'slug', currentLang, mainLang) || ''
|
|
233
|
+
var slug = resolveI18nField(record, 'slug', 'slug', currentLang, mainLang) || ''${
|
|
234
|
+
options.productPages
|
|
235
|
+
? `
|
|
236
|
+
var productPageKey = resolveProductPageKey(pickFirst(record.product_page, record.productPage))`
|
|
237
|
+
: ''
|
|
238
|
+
}
|
|
116
239
|
var description = resolveI18nField(record, 'description', 'description', currentLang, mainLang) || ''
|
|
117
240
|
var category = resolveI18nField(record, 'category', 'category', currentLang, mainLang) || null
|
|
118
241
|
var tagsRaw = resolveI18nField(record, 'tags', 'tags', currentLang, mainLang)
|
|
@@ -145,7 +268,32 @@ function buildEcommerceProduct(record, options) {
|
|
|
145
268
|
var paymentType = pickFirst(record.payment_type, record.paymentType) || 'one_time'
|
|
146
269
|
var recurringInterval = pickFirst(record.recurring_interval, record.recurringInterval) || null
|
|
147
270
|
var rawRecurringCount = pickFirst(record.recurring_interval_count, record.recurringIntervalCount)
|
|
148
|
-
var
|
|
271
|
+
var isRecurring = String(paymentType).trim().toLowerCase() === 'recurring'
|
|
272
|
+
// A recurring product's count is a whole number of intervals (anything
|
|
273
|
+
// unreadable is 1); a one-time product keeps the column as stored.
|
|
274
|
+
var recurringIntervalCount = isRecurring
|
|
275
|
+
? normalizeBillingIntervalCountValue(rawRecurringCount)
|
|
276
|
+
: safeNumber(rawRecurringCount, null)
|
|
277
|
+
// What a recurring price is followed by ("month", "3 months"; '' for a
|
|
278
|
+
// one-time product), the purchase-details sentence ("Billed every 3 months ·
|
|
279
|
+
// 14-day free trial"; '' for a one-time product), the free trial in days (0
|
|
280
|
+
// for none), and whether the product is delivered as a download or issued
|
|
281
|
+
// as a gift card. Strings where a rendering condition reads them, like
|
|
282
|
+
// hasDiscount: a bound text node prints the period verbatim and a rendering
|
|
283
|
+
// condition compares the flags as strings. MUST mirror
|
|
284
|
+
// packages/renderer/src/utils/ecommerce-products.ts, whose period comes from
|
|
285
|
+
// formatBillingPeriod in constants/subscriptions.ts.
|
|
286
|
+
var billingPeriod = isRecurring
|
|
287
|
+
? formatBillingPeriodLabel(recurringInterval, recurringIntervalCount)
|
|
288
|
+
: ''
|
|
289
|
+
var trialDays = resolvePositiveCountValue(pickFirst(record.trial_days, record.trialDays))
|
|
290
|
+
var billingSummary = isRecurring
|
|
291
|
+
? formatBillingSummaryLabel(recurringInterval, recurringIntervalCount, trialDays)
|
|
292
|
+
: ''
|
|
293
|
+
var isDigital = readBooleanFlag(pickFirst(record.is_digital, record.isDigital)) ? 'true' : 'false'
|
|
294
|
+
var isGiftCard = readBooleanFlag(pickFirst(record.is_gift_card, record.isGiftCard))
|
|
295
|
+
? 'true'
|
|
296
|
+
: 'false'
|
|
149
297
|
|
|
150
298
|
// Physical product fields
|
|
151
299
|
var sku = record.sku || null
|
|
@@ -503,6 +651,23 @@ function buildEcommerceProduct(record, options) {
|
|
|
503
651
|
var variantsJson = safeStringifyJson(variants)
|
|
504
652
|
var variantsDisplayJson = safeStringifyJson(variantsDisplay)
|
|
505
653
|
|
|
654
|
+
// Product options (configurator, configured price, card quick-add gate): the
|
|
655
|
+
// three flags always, the detail only for the details page's single-record
|
|
656
|
+
// fetch (options.includeOptionDetail). See buildProductOptionFields below.
|
|
657
|
+
var optionFields = buildProductOptionFields(record, {
|
|
658
|
+
productId: id != null ? String(id) : '',
|
|
659
|
+
currentLanguage: currentLang,
|
|
660
|
+
mainLanguage: mainLang,
|
|
661
|
+
includeDetail: options.includeOptionDetail === true,
|
|
662
|
+
basePrice: price,
|
|
663
|
+
activeDiscount: activeDiscount,
|
|
664
|
+
variants: variants,
|
|
665
|
+
defaultVariant: firstVariant,
|
|
666
|
+
symbol: currencySymbol,
|
|
667
|
+
position: getCurrencyInfo(currency).position,
|
|
668
|
+
assetMap: assetMap,
|
|
669
|
+
})
|
|
670
|
+
|
|
506
671
|
return {
|
|
507
672
|
id: id,
|
|
508
673
|
name: name,
|
|
@@ -518,6 +683,11 @@ function buildEcommerceProduct(record, options) {
|
|
|
518
683
|
paymentType: paymentType,
|
|
519
684
|
recurringInterval: recurringInterval,
|
|
520
685
|
recurringIntervalCount: recurringIntervalCount,
|
|
686
|
+
billingPeriod: billingPeriod,
|
|
687
|
+
trialDays: trialDays,
|
|
688
|
+
billingSummary: billingSummary,
|
|
689
|
+
isDigital: isDigital,
|
|
690
|
+
isGiftCard: isGiftCard,
|
|
521
691
|
sku: sku,
|
|
522
692
|
weight: weight,
|
|
523
693
|
weightUnit: weightUnit,
|
|
@@ -579,6 +749,27 @@ function buildEcommerceProduct(record, options) {
|
|
|
579
749
|
// THIS FILE — it is one template literal, and a backtick in a comment ends
|
|
580
750
|
// the string.
|
|
581
751
|
reviews: reviews,
|
|
752
|
+
// Product options — see buildProductOptionFields.
|
|
753
|
+
hasProductOptions: optionFields.hasProductOptions,
|
|
754
|
+
optionsRequireInput: optionFields.optionsRequireInput,
|
|
755
|
+
optionsQuickAdd: optionFields.optionsQuickAdd,
|
|
756
|
+
optionGroups: optionFields.optionGroups,
|
|
757
|
+
optionGroupsJson: optionFields.optionGroupsJson,
|
|
758
|
+
optionsPricingJson: optionFields.optionsPricingJson,
|
|
759
|
+
optionsDefaultDisplayPrice: optionFields.optionsDefaultDisplayPrice,
|
|
760
|
+
optionsDefaultOriginalDisplayPrice: optionFields.optionsDefaultOriginalDisplayPrice,
|
|
761
|
+
optionsDefaultSummary: optionFields.optionsDefaultSummary,
|
|
762
|
+
optionsFreshKey: optionFields.optionsFreshKey,
|
|
763
|
+
optionsValidKey: optionFields.optionsValidKey,
|
|
764
|
+
optionsOriginalKey: optionFields.optionsOriginalKey,
|
|
765
|
+
optionsSummaryKey: optionFields.optionsSummaryKey,
|
|
766
|
+
optionsFilesKey: optionFields.optionsFilesKey,${
|
|
767
|
+
options.productPages
|
|
768
|
+
? `
|
|
769
|
+
productPageKey: productPageKey,
|
|
770
|
+
productPageUrl: buildProductPageUrl(productPageKey, { slug: slug, id: id }, record),`
|
|
771
|
+
: ''
|
|
772
|
+
}
|
|
582
773
|
}
|
|
583
774
|
}
|
|
584
775
|
|
|
@@ -653,6 +844,378 @@ function safeStringifyJson(obj) {
|
|
|
653
844
|
}
|
|
654
845
|
}
|
|
655
846
|
|
|
847
|
+
// --- Product options -------------------------------------------------------
|
|
848
|
+
// MUST mirror buildStorefrontProductOptionFields in the GUI's
|
|
849
|
+
// features/e-commerce/utils/product-options/product-options-storefront.ts
|
|
850
|
+
// (the canvas copy): both run over __tests__/product-options-storefront.fixture.json.
|
|
851
|
+
//
|
|
852
|
+
// Always emitted: hasProductOptions, optionsRequireInput, optionsQuickAdd.
|
|
853
|
+
// Everything else is details-page only, so a grid of cards never ships every
|
|
854
|
+
// product's option tree. Structure and prices come from the main option_groups;
|
|
855
|
+
// the request language's clone only overlays strings, so a stale translation
|
|
856
|
+
// can never misprice. Flags are 'true'/'false' STRINGS, prices gross 2-decimal
|
|
857
|
+
// strings, and every state key is scoped by the product id.
|
|
858
|
+
|
|
859
|
+
// The configurator's state-key prefixes. MUST mirror OPTION_KEY_PREFIX in the
|
|
860
|
+
// GUI's features/e-commerce/utils/product-options/product-options-uidl.ts.
|
|
861
|
+
var OPTION_STATE_KEY_PREFIX = {
|
|
862
|
+
selected: 'sel:',
|
|
863
|
+
hidden: 'hide:',
|
|
864
|
+
shown: 'show:',
|
|
865
|
+
missing: 'miss:',
|
|
866
|
+
invalid: 'inv:',
|
|
867
|
+
rejected: 'rej:',
|
|
868
|
+
failed: 'fail:',
|
|
869
|
+
uploaded: 'up:',
|
|
870
|
+
uploading: 'load:',
|
|
871
|
+
fresh: 'fresh:',
|
|
872
|
+
valid: 'valid:',
|
|
873
|
+
original: 'orig:',
|
|
874
|
+
summary: 'sum:',
|
|
875
|
+
files: 'files:',
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// A product without options, and the detail-free shape a listing gets.
|
|
879
|
+
function emptyProductOptionFields() {
|
|
880
|
+
return {
|
|
881
|
+
hasProductOptions: 'false',
|
|
882
|
+
optionsRequireInput: 'false',
|
|
883
|
+
optionsQuickAdd: 'true',
|
|
884
|
+
optionGroups: [],
|
|
885
|
+
optionGroupsJson: '[]',
|
|
886
|
+
optionsPricingJson: '{}',
|
|
887
|
+
optionsDefaultDisplayPrice: '',
|
|
888
|
+
optionsDefaultOriginalDisplayPrice: '',
|
|
889
|
+
optionsDefaultSummary: '',
|
|
890
|
+
optionsFreshKey: '',
|
|
891
|
+
optionsValidKey: '',
|
|
892
|
+
optionsOriginalKey: '',
|
|
893
|
+
optionsSummaryKey: '',
|
|
894
|
+
optionsFilesKey: '',
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function buildProductOptionFields(record, ctx) {
|
|
899
|
+
var groups = __poParseGroups(pickFirst(record.option_groups, record.optionGroups))
|
|
900
|
+
if (groups.length === 0) return emptyProductOptionFields()
|
|
901
|
+
// Gift cards and subscriptions may carry options, never priced ones.
|
|
902
|
+
var pricesDisabled =
|
|
903
|
+
__poFlag(pickFirst(record.is_gift_card, record.isGiftCard)) ||
|
|
904
|
+
__poText(pickFirst(record.payment_type, record.paymentType)).trim().toLowerCase() === 'recurring'
|
|
905
|
+
var defaultNet = ctx.defaultVariant
|
|
906
|
+
? optionChargedNet(ctx.defaultVariant, ctx)
|
|
907
|
+
: __pdDiscountedPrice(ctx.basePrice, ctx.activeDiscount)
|
|
908
|
+
var fields = emptyProductOptionFields()
|
|
909
|
+
fields.hasProductOptions = 'true'
|
|
910
|
+
fields.optionsRequireInput = __poRequiresInput(groups) ? 'true' : 'false'
|
|
911
|
+
fields.optionsQuickAdd = __poQuickAdd(groups, defaultNet, pricesDisabled) ? 'true' : 'false'
|
|
912
|
+
if (!ctx.includeDetail) return fields
|
|
913
|
+
|
|
914
|
+
var localized = __poLocalize({ main: groups, clone: readOptionGroupsClone(record, ctx) })
|
|
915
|
+
var initial = __poResolve(defaultNet, localized, __poDefaultEntries(localized), pricesDisabled)
|
|
916
|
+
var view = {
|
|
917
|
+
productId: ctx.productId,
|
|
918
|
+
freshKey: OPTION_STATE_KEY_PREFIX.fresh + ctx.productId,
|
|
919
|
+
// The default answers: which price variant is in force before the shopper
|
|
920
|
+
// touches anything.
|
|
921
|
+
selection: __poDefaultSelection(localized),
|
|
922
|
+
visibleKeys: initial.visibleKeys,
|
|
923
|
+
pricesDisabled: pricesDisabled,
|
|
924
|
+
symbol: ctx.symbol,
|
|
925
|
+
position: ctx.position,
|
|
926
|
+
assetMap: ctx.assetMap,
|
|
927
|
+
}
|
|
928
|
+
var defaultOriginalNet = ctx.defaultVariant
|
|
929
|
+
? optionListNet(ctx.defaultVariant, ctx)
|
|
930
|
+
: __pdDiscountedPrice(ctx.basePrice, null)
|
|
931
|
+
var optionGroups = []
|
|
932
|
+
for (var gi = 0; gi < localized.length; gi++) {
|
|
933
|
+
optionGroups.push(buildOptionGroupView(localized[gi], view))
|
|
934
|
+
}
|
|
935
|
+
fields.optionGroups = optionGroups
|
|
936
|
+
fields.optionGroupsJson = __poSerialize(withResolvedOptionValueImages(localized, ctx.assetMap)) || '[]'
|
|
937
|
+
fields.optionsPricingJson = JSON.stringify(buildOptionPricing(ctx, pricesDisabled))
|
|
938
|
+
fields.optionsDefaultDisplayPrice = optionDisplayMoney(defaultNet + initial.delta)
|
|
939
|
+
fields.optionsDefaultOriginalDisplayPrice = ctx.activeDiscount
|
|
940
|
+
? optionDisplayMoney(defaultOriginalNet + initial.delta)
|
|
941
|
+
: ''
|
|
942
|
+
fields.optionsDefaultSummary = initial.label
|
|
943
|
+
fields.optionsFreshKey = view.freshKey
|
|
944
|
+
fields.optionsValidKey = OPTION_STATE_KEY_PREFIX.valid + ctx.productId
|
|
945
|
+
fields.optionsOriginalKey = OPTION_STATE_KEY_PREFIX.original + ctx.productId
|
|
946
|
+
fields.optionsSummaryKey = OPTION_STATE_KEY_PREFIX.summary + ctx.productId
|
|
947
|
+
fields.optionsFilesKey = OPTION_STATE_KEY_PREFIX.files + ctx.productId
|
|
948
|
+
return fields
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function buildOptionGroupView(group, view) {
|
|
952
|
+
var type = group.type
|
|
953
|
+
var isChoice = type === 'choice'
|
|
954
|
+
var isMulti = type === 'multi-choice'
|
|
955
|
+
var isToggle = type === 'toggle'
|
|
956
|
+
var listsValues = isChoice || isMulti
|
|
957
|
+
var isSwatches = listsValues && group.display === 'swatches'
|
|
958
|
+
var isCards = listsValues && group.display === 'cards'
|
|
959
|
+
var isSelectable = listsValues || isToggle
|
|
960
|
+
var scope = view.productId + '|' + group.key
|
|
961
|
+
var priceVariants =
|
|
962
|
+
__poPricedTypes.indexOf(type) !== -1
|
|
963
|
+
? buildOptionPriceVariants(group, group.key, null, type === 'number' ? group.unit : undefined, view)
|
|
964
|
+
: []
|
|
965
|
+
var acceptKind = type === 'upload' ? group.accept || 'any' : ''
|
|
966
|
+
var values = []
|
|
967
|
+
if (listsValues) {
|
|
968
|
+
var groupValues = group.values || []
|
|
969
|
+
for (var vi = 0; vi < groupValues.length; vi++) {
|
|
970
|
+
values.push(buildOptionValueView(group, groupValues[vi], isSwatches, isCards, view))
|
|
971
|
+
}
|
|
972
|
+
} else if (isToggle) {
|
|
973
|
+
values.push(buildOptionToggleValue(group, priceVariants, view))
|
|
974
|
+
}
|
|
975
|
+
var input = describeOptionInput(group)
|
|
976
|
+
return {
|
|
977
|
+
id: scope,
|
|
978
|
+
key: group.key,
|
|
979
|
+
label: group.label,
|
|
980
|
+
helpText: group.helpText || '',
|
|
981
|
+
placeholder: group.placeholder || '',
|
|
982
|
+
unit: group.unit || '',
|
|
983
|
+
required: group.required === true ? 'true' : 'false',
|
|
984
|
+
isChoice: isChoice ? 'true' : 'false',
|
|
985
|
+
isMulti: isMulti ? 'true' : 'false',
|
|
986
|
+
isText: type === 'text' ? 'true' : 'false',
|
|
987
|
+
isLongText: type === 'long-text' ? 'true' : 'false',
|
|
988
|
+
isNumber: type === 'number' ? 'true' : 'false',
|
|
989
|
+
isToggle: isToggle ? 'true' : 'false',
|
|
990
|
+
isUpload: type === 'upload' ? 'true' : 'false',
|
|
991
|
+
isButtons: isSelectable && !isSwatches && !isCards ? 'true' : 'false',
|
|
992
|
+
isSwatches: isSwatches ? 'true' : 'false',
|
|
993
|
+
isCards: isCards ? 'true' : 'false',
|
|
994
|
+
isSelectable: isSelectable ? 'true' : 'false',
|
|
995
|
+
isInput: type === 'text' || type === 'number' ? 'true' : 'false',
|
|
996
|
+
initiallyVisible: view.visibleKeys.indexOf(group.key) !== -1 ? 'true' : 'false',
|
|
997
|
+
cellRole: isChoice ? 'radio' : isMulti ? 'checkbox' : isToggle ? 'switch' : '',
|
|
998
|
+
rowRole: isChoice ? 'radiogroup' : 'group',
|
|
999
|
+
// aria-required is allowed on a radiogroup only: a group row gets null, which
|
|
1000
|
+
// renders no attribute (never undefined, which getStaticProps cannot serialize).
|
|
1001
|
+
rowRequired: isChoice ? (group.required === true ? 'true' : 'false') : null,
|
|
1002
|
+
idleTabIndex: isChoice ? '-1' : '0',
|
|
1003
|
+
inputType: input.inputType,
|
|
1004
|
+
inputMode: input.inputMode,
|
|
1005
|
+
maxLengthText: input.maxLengthText,
|
|
1006
|
+
minText: input.minText,
|
|
1007
|
+
maxText: input.maxText,
|
|
1008
|
+
stepText: input.stepText,
|
|
1009
|
+
defaultValueText: input.defaultValueText,
|
|
1010
|
+
rangeHint: input.rangeHint,
|
|
1011
|
+
acceptKind: acceptKind,
|
|
1012
|
+
acceptMime: acceptKind ? __poUploadMime[acceptKind].join(',') : '',
|
|
1013
|
+
priceVariants: priceVariants,
|
|
1014
|
+
headerPriceVariants: isSelectable ? [] : priceVariants,
|
|
1015
|
+
hiddenKey: OPTION_STATE_KEY_PREFIX.hidden + scope,
|
|
1016
|
+
shownKey: OPTION_STATE_KEY_PREFIX.shown + scope,
|
|
1017
|
+
missingKey: OPTION_STATE_KEY_PREFIX.missing + scope,
|
|
1018
|
+
invalidKey: OPTION_STATE_KEY_PREFIX.invalid + scope,
|
|
1019
|
+
rejectedKey: OPTION_STATE_KEY_PREFIX.rejected + scope,
|
|
1020
|
+
failedKey: OPTION_STATE_KEY_PREFIX.failed + scope,
|
|
1021
|
+
uploadedKey: OPTION_STATE_KEY_PREFIX.uploaded + scope,
|
|
1022
|
+
// Product-level: one upload at a time blocks every upload button.
|
|
1023
|
+
uploadingKey: OPTION_STATE_KEY_PREFIX.uploading + view.productId,
|
|
1024
|
+
freshKey: view.freshKey,
|
|
1025
|
+
values: values,
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// The groups as the page's resolver reads them (data-option-groups), with each
|
|
1030
|
+
// value's picture as a URL the browser can load: the shopper's choices hand it
|
|
1031
|
+
// on to anything on the page that shows the chosen value's picture. A picture
|
|
1032
|
+
// that resolves to nothing is dropped, never shipped as a bare asset id. TWIN
|
|
1033
|
+
// of withResolvedValueImages in the GUI's product-options-storefront.ts.
|
|
1034
|
+
function withResolvedOptionValueImages(groups, assetMap) {
|
|
1035
|
+
var out = []
|
|
1036
|
+
for (var gi = 0; gi < groups.length; gi++) {
|
|
1037
|
+
var group = groups[gi]
|
|
1038
|
+
if (!Array.isArray(group.values)) {
|
|
1039
|
+
out.push(group)
|
|
1040
|
+
continue
|
|
1041
|
+
}
|
|
1042
|
+
var values = []
|
|
1043
|
+
for (var vi = 0; vi < group.values.length; vi++) {
|
|
1044
|
+
var value = group.values[vi]
|
|
1045
|
+
if (!value.imageUrl) {
|
|
1046
|
+
values.push(value)
|
|
1047
|
+
continue
|
|
1048
|
+
}
|
|
1049
|
+
var valueCopy = {}
|
|
1050
|
+
for (var vk in value) {
|
|
1051
|
+
if (Object.prototype.hasOwnProperty.call(value, vk) && vk !== 'imageUrl') valueCopy[vk] = value[vk]
|
|
1052
|
+
}
|
|
1053
|
+
var resolved = resolveMediaUrl(value.imageUrl, assetMap) || ''
|
|
1054
|
+
if (resolved) valueCopy.imageUrl = resolved
|
|
1055
|
+
values.push(valueCopy)
|
|
1056
|
+
}
|
|
1057
|
+
var groupCopy = {}
|
|
1058
|
+
for (var gk in group) {
|
|
1059
|
+
if (Object.prototype.hasOwnProperty.call(group, gk)) groupCopy[gk] = group[gk]
|
|
1060
|
+
}
|
|
1061
|
+
groupCopy.values = values
|
|
1062
|
+
out.push(groupCopy)
|
|
1063
|
+
}
|
|
1064
|
+
return out
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function buildOptionValueView(group, value, isSwatches, isCards, view) {
|
|
1068
|
+
var scope = view.productId + '|' + group.key + '|' + value.key
|
|
1069
|
+
var color = value.color || ''
|
|
1070
|
+
// An asset id that resolves to nothing is never shipped as a bare id.
|
|
1071
|
+
var imageUrl = value.imageUrl ? resolveMediaUrl(value.imageUrl, view.assetMap) || '' : ''
|
|
1072
|
+
return {
|
|
1073
|
+
id: scope,
|
|
1074
|
+
key: value.key,
|
|
1075
|
+
label: value.label,
|
|
1076
|
+
description: value.description || '',
|
|
1077
|
+
color: color,
|
|
1078
|
+
imageUrl: imageUrl,
|
|
1079
|
+
isDefault: value['default'] === true ? 'true' : 'false',
|
|
1080
|
+
showSwatch: isSwatches && color !== '' ? 'true' : 'false',
|
|
1081
|
+
showImage: isCards && imageUrl !== '' ? 'true' : 'false',
|
|
1082
|
+
selectedKey: OPTION_STATE_KEY_PREFIX.selected + scope,
|
|
1083
|
+
freshKey: view.freshKey,
|
|
1084
|
+
priceVariants: buildOptionPriceVariants(value, group.key, value.key, undefined, view),
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// A toggle is one switch cell: its label is the group's, its price the group's price.
|
|
1089
|
+
function buildOptionToggleValue(group, priceVariants, view) {
|
|
1090
|
+
var scope = view.productId + '|' + group.key + '|on'
|
|
1091
|
+
return {
|
|
1092
|
+
id: scope,
|
|
1093
|
+
key: 'on',
|
|
1094
|
+
label: group.label,
|
|
1095
|
+
description: '',
|
|
1096
|
+
color: '',
|
|
1097
|
+
imageUrl: '',
|
|
1098
|
+
isDefault: 'false',
|
|
1099
|
+
showSwatch: 'false',
|
|
1100
|
+
showImage: 'false',
|
|
1101
|
+
selectedKey: OPTION_STATE_KEY_PREFIX.selected + scope,
|
|
1102
|
+
freshKey: view.freshKey,
|
|
1103
|
+
priceVariants: priceVariants,
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// One entry per priceWhen override (in order), then the default price ('d').
|
|
1108
|
+
// The configurator shows the entry whose activeKey the resolver wrote, or the
|
|
1109
|
+
// isInitial one before the shopper touched anything.
|
|
1110
|
+
function buildOptionPriceVariants(target, groupKey, valueKey, unit, view) {
|
|
1111
|
+
var initial = __poEffectiveVariant(target, view.selection, view.visibleKeys)
|
|
1112
|
+
var overrides = Array.isArray(target.priceWhen) ? target.priceWhen : []
|
|
1113
|
+
var variants = []
|
|
1114
|
+
for (var oi = 0; oi < overrides.length; oi++) {
|
|
1115
|
+
variants.push(buildOptionPriceVariant(overrides[oi].price, oi, initial, groupKey, valueKey, unit, view))
|
|
1116
|
+
}
|
|
1117
|
+
variants.push(buildOptionPriceVariant(target.price, 'd', initial, groupKey, valueKey, unit, view))
|
|
1118
|
+
return variants
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function buildOptionPriceVariant(rule, variant, initial, groupKey, valueKey, unit, view) {
|
|
1122
|
+
return {
|
|
1123
|
+
activeKey: __poPriceVariantKey(view.productId, groupKey, valueKey, variant),
|
|
1124
|
+
label: view.pricesDisabled
|
|
1125
|
+
? ''
|
|
1126
|
+
: __poPriceLabel(rule, view.symbol, view.position, STOREFRONT_TAX_RATE, unit),
|
|
1127
|
+
isInitial: variant === initial ? 'true' : 'false',
|
|
1128
|
+
// Repeated on every variant: the badge gates inside this list's mapper can
|
|
1129
|
+
// only read the list's own item.
|
|
1130
|
+
freshKey: view.freshKey,
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function describeOptionInput(group) {
|
|
1135
|
+
var input = {
|
|
1136
|
+
inputType: '',
|
|
1137
|
+
inputMode: '',
|
|
1138
|
+
maxLengthText: '',
|
|
1139
|
+
minText: '',
|
|
1140
|
+
maxText: '',
|
|
1141
|
+
stepText: '',
|
|
1142
|
+
defaultValueText: '',
|
|
1143
|
+
rangeHint: '',
|
|
1144
|
+
}
|
|
1145
|
+
if (group.type === 'text' || group.type === 'long-text') {
|
|
1146
|
+
var limit = group.type === 'text' ? __poLimits.text : __poLimits.longText
|
|
1147
|
+
input.inputType = 'text'
|
|
1148
|
+
input.inputMode = 'text'
|
|
1149
|
+
input.maxLengthText = String(typeof group.maxLength === 'number' ? Math.min(limit, group.maxLength) : limit)
|
|
1150
|
+
} else if (group.type === 'number') {
|
|
1151
|
+
input.inputType = 'number'
|
|
1152
|
+
input.inputMode = 'decimal'
|
|
1153
|
+
input.minText = typeof group.min === 'number' ? String(group.min) : ''
|
|
1154
|
+
input.maxText = typeof group.max === 'number' ? String(group.max) : ''
|
|
1155
|
+
input.stepText = typeof group.step === 'number' ? String(group.step) : 'any'
|
|
1156
|
+
input.defaultValueText = typeof group.defaultValue === 'number' ? String(group.defaultValue) : ''
|
|
1157
|
+
input.rangeHint = describeOptionRange(input.minText, input.maxText, group.unit || '')
|
|
1158
|
+
}
|
|
1159
|
+
return input
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// "10 – 200 cm", "≥ 10 cm", "≤ 200 cm"; '' without bounds.
|
|
1163
|
+
function describeOptionRange(minText, maxText, unit) {
|
|
1164
|
+
var range = ''
|
|
1165
|
+
if (minText && maxText) range = minText + ' – ' + maxText
|
|
1166
|
+
else if (minText) range = '≥ ' + minText
|
|
1167
|
+
else if (maxText) range = '≤ ' + maxText
|
|
1168
|
+
return range && unit ? range + ' ' + unit : range
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// What the configurator's resolver needs to price the combination the shopper
|
|
1172
|
+
// picks in the variant picker: every combination, charged and list NET.
|
|
1173
|
+
function buildOptionPricing(ctx, pricesDisabled) {
|
|
1174
|
+
var discounted = !!ctx.activeDiscount
|
|
1175
|
+
var variants = []
|
|
1176
|
+
for (var pi = 0; pi < ctx.variants.length; pi++) {
|
|
1177
|
+
var variant = ctx.variants[pi]
|
|
1178
|
+
variants.push({
|
|
1179
|
+
id: variant.id,
|
|
1180
|
+
options: variant.options,
|
|
1181
|
+
net: optionChargedNet(variant, ctx),
|
|
1182
|
+
originalNet: discounted ? optionListNet(variant, ctx) : null,
|
|
1183
|
+
})
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
productId: ctx.productId,
|
|
1187
|
+
netBase: __pdDiscountedPrice(ctx.basePrice, ctx.activeDiscount),
|
|
1188
|
+
originalNetBase: discounted ? __pdDiscountedPrice(ctx.basePrice, null) : null,
|
|
1189
|
+
taxRate: STOREFRONT_TAX_RATE,
|
|
1190
|
+
symbol: ctx.symbol,
|
|
1191
|
+
position: ctx.position,
|
|
1192
|
+
pricesDisabled: pricesDisabled,
|
|
1193
|
+
defaultCombinationId: ctx.defaultVariant ? ctx.defaultVariant.id : '',
|
|
1194
|
+
variants: variants,
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// A combination's own price, else the product's (a null price inherits).
|
|
1199
|
+
function optionVariantListPrice(variant, basePrice) {
|
|
1200
|
+
return typeof variant.price === 'number' && isFinite(variant.price) ? variant.price : basePrice
|
|
1201
|
+
}
|
|
1202
|
+
function optionChargedNet(variant, ctx) {
|
|
1203
|
+
return __pdDiscountedPrice(optionVariantListPrice(variant, ctx.basePrice), ctx.activeDiscount)
|
|
1204
|
+
}
|
|
1205
|
+
function optionListNet(variant, ctx) {
|
|
1206
|
+
return __pdDiscountedPrice(optionVariantListPrice(variant, ctx.basePrice), null)
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function readOptionGroupsClone(record, ctx) {
|
|
1210
|
+
if (!ctx.currentLanguage || !ctx.mainLanguage || ctx.currentLanguage === ctx.mainLanguage) return null
|
|
1211
|
+
return record[ctx.currentLanguage + '_option_groups']
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// A unit price as the shopper reads it: NET rounded to the cent, then gross.
|
|
1215
|
+
function optionDisplayMoney(net) {
|
|
1216
|
+
return applyStorefrontTax(__poRound2(net)).toFixed(2)
|
|
1217
|
+
}
|
|
1218
|
+
|
|
656
1219
|
// Parse + normalize the variant_options JSON into render-ready axes. Tolerant of
|
|
657
1220
|
// null / malformed / partial data (returns []). Keeps ONLY the fields the pill
|
|
658
1221
|
// widget needs; the labels are already language-resolved by the caller.
|