medusa-storefront-data 2.3.0 → 2.4.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/HOMEPAGE_CONFIG.md +99 -0
- package/dist/segment-data/default-page-input.json +191 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/cookies.d.ts.map +1 -0
- package/dist/src/edge.d.ts.map +1 -0
- package/dist/src/middleware.d.ts.map +1 -0
- package/dist/src/server/cart.d.ts.map +1 -0
- package/dist/src/server/categories.d.ts.map +1 -0
- package/dist/src/server/collections.d.ts.map +1 -0
- package/dist/src/server/config-merge.d.ts +3 -0
- package/dist/src/server/config-merge.d.ts.map +1 -0
- package/dist/src/server/config-merge.js +31 -0
- package/dist/src/server/contact.d.ts.map +1 -0
- package/dist/src/server/customer-registration.d.ts.map +1 -0
- package/dist/src/server/customer.d.ts.map +1 -0
- package/dist/src/server/dynamic-config.d.ts +240 -0
- package/dist/src/server/dynamic-config.d.ts.map +1 -0
- package/dist/src/server/dynamic-config.js +703 -0
- package/dist/src/server/fulfillment.d.ts.map +1 -0
- package/dist/src/server/guest.d.ts.map +1 -0
- package/dist/src/server/home-sections/about-brand.d.ts +9 -0
- package/dist/src/server/home-sections/about-brand.d.ts.map +1 -0
- package/dist/src/server/home-sections/about-brand.js +9 -0
- package/dist/src/server/home-sections/baptism-picks.d.ts +10 -0
- package/dist/src/server/home-sections/baptism-picks.d.ts.map +1 -0
- package/dist/src/server/home-sections/baptism-picks.js +27 -0
- package/dist/src/server/home-sections/baptism.d.ts +13 -0
- package/dist/src/server/home-sections/baptism.d.ts.map +1 -0
- package/dist/src/server/home-sections/baptism.js +43 -0
- package/dist/src/server/home-sections/blog-posts.d.ts +9 -0
- package/dist/src/server/home-sections/blog-posts.d.ts.map +1 -0
- package/dist/src/server/home-sections/blog-posts.js +9 -0
- package/dist/src/server/home-sections/brand-marquee.d.ts +8 -0
- package/dist/src/server/home-sections/brand-marquee.d.ts.map +1 -0
- package/dist/src/server/home-sections/brand-marquee.js +7 -0
- package/dist/src/server/home-sections/brand-pillars.d.ts +9 -0
- package/dist/src/server/home-sections/brand-pillars.d.ts.map +1 -0
- package/dist/src/server/home-sections/brand-pillars.js +9 -0
- package/dist/src/server/home-sections/category-pills.d.ts +9 -0
- package/dist/src/server/home-sections/category-pills.d.ts.map +1 -0
- package/dist/src/server/home-sections/category-pills.js +19 -0
- package/dist/src/server/home-sections/celebrity-trust.d.ts +12 -0
- package/dist/src/server/home-sections/celebrity-trust.d.ts.map +1 -0
- package/dist/src/server/home-sections/celebrity-trust.js +27 -0
- package/dist/src/server/home-sections/features.d.ts +9 -0
- package/dist/src/server/home-sections/features.d.ts.map +1 -0
- package/dist/src/server/home-sections/features.js +9 -0
- package/dist/src/server/home-sections/hero.d.ts +9 -0
- package/dist/src/server/home-sections/hero.d.ts.map +1 -0
- package/dist/src/server/home-sections/hero.js +9 -0
- package/dist/src/server/home-sections/instagram-posts.d.ts +9 -0
- package/dist/src/server/home-sections/instagram-posts.d.ts.map +1 -0
- package/dist/src/server/home-sections/instagram-posts.js +9 -0
- package/dist/src/server/home-sections/loved-by-moms.d.ts +12 -0
- package/dist/src/server/home-sections/loved-by-moms.d.ts.map +1 -0
- package/dist/src/server/home-sections/loved-by-moms.js +20 -0
- package/dist/src/server/home-sections/luxe-favourites.d.ts +11 -0
- package/dist/src/server/home-sections/luxe-favourites.d.ts.map +1 -0
- package/dist/src/server/home-sections/luxe-favourites.js +19 -0
- package/dist/src/server/home-sections/new-arrivals-classic.d.ts +8 -0
- package/dist/src/server/home-sections/new-arrivals-classic.d.ts.map +1 -0
- package/dist/src/server/home-sections/new-arrivals-classic.js +10 -0
- package/dist/src/server/home-sections/new-arrivals.d.ts +12 -0
- package/dist/src/server/home-sections/new-arrivals.d.ts.map +1 -0
- package/dist/src/server/home-sections/new-arrivals.js +28 -0
- package/dist/src/server/home-sections/promo-announcements.d.ts +7 -0
- package/dist/src/server/home-sections/promo-announcements.d.ts.map +1 -0
- package/dist/src/server/home-sections/promo-announcements.js +7 -0
- package/dist/src/server/home-sections/promo-countdown.d.ts +6 -0
- package/dist/src/server/home-sections/promo-countdown.d.ts.map +1 -0
- package/dist/src/server/home-sections/promo-countdown.js +5 -0
- package/dist/src/server/home-sections/shared.d.ts +10 -0
- package/dist/src/server/home-sections/shared.d.ts.map +1 -0
- package/dist/src/server/home-sections/shared.js +29 -0
- package/dist/src/server/home-sections/shop-by-age.d.ts +9 -0
- package/dist/src/server/home-sections/shop-by-age.d.ts.map +1 -0
- package/dist/src/server/home-sections/shop-by-age.js +17 -0
- package/dist/src/server/home-sections/shop-by-category.d.ts +10 -0
- package/dist/src/server/home-sections/shop-by-category.d.ts.map +1 -0
- package/dist/src/server/home-sections/shop-by-category.js +17 -0
- package/dist/src/server/home-sections/testimonials.d.ts +9 -0
- package/dist/src/server/home-sections/testimonials.d.ts.map +1 -0
- package/dist/src/server/home-sections/testimonials.js +9 -0
- package/dist/src/server/home-sections/theme-dresses.d.ts +12 -0
- package/dist/src/server/home-sections/theme-dresses.d.ts.map +1 -0
- package/dist/src/server/home-sections/theme-dresses.js +27 -0
- package/dist/src/server/home-sections/video-stories.d.ts +9 -0
- package/dist/src/server/home-sections/video-stories.d.ts.map +1 -0
- package/dist/src/server/home-sections/video-stories.js +9 -0
- package/dist/src/server/home-sections/why-choose-us.d.ts +9 -0
- package/dist/src/server/home-sections/why-choose-us.d.ts.map +1 -0
- package/dist/src/server/home-sections/why-choose-us.js +9 -0
- package/dist/src/server/home.d.ts +62 -0
- package/dist/src/server/home.d.ts.map +1 -0
- package/dist/{server → src/server}/home.js +29 -31
- package/dist/src/server/homepage-config.types.d.ts +274 -0
- package/dist/src/server/homepage-config.types.d.ts.map +1 -0
- package/dist/src/server/homepage-config.types.js +33 -0
- package/dist/{server → src/server}/homepage-section-defaults.d.ts +35 -2
- package/dist/src/server/homepage-section-defaults.d.ts.map +1 -0
- package/dist/{server → src/server}/homepage-section-defaults.js +41 -2
- package/dist/{server → src/server}/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/{server → src/server}/index.js +2 -0
- package/dist/src/server/locale-actions.d.ts.map +1 -0
- package/dist/src/server/locales.d.ts.map +1 -0
- package/dist/src/server/notifications.d.ts.map +1 -0
- package/dist/src/server/onboarding.d.ts.map +1 -0
- package/dist/src/server/orders.d.ts.map +1 -0
- package/dist/src/server/page-input.d.ts +19 -0
- package/dist/src/server/page-input.d.ts.map +1 -0
- package/dist/src/server/page-input.js +40 -0
- package/dist/src/server/payment-details.d.ts.map +1 -0
- package/dist/src/server/payment.d.ts.map +1 -0
- package/dist/src/server/pincode.d.ts.map +1 -0
- package/dist/src/server/products.d.ts.map +1 -0
- package/dist/src/server/regions.d.ts.map +1 -0
- package/dist/src/server/resolve-home-segment-data.d.ts +25 -0
- package/dist/src/server/resolve-home-segment-data.d.ts.map +1 -0
- package/dist/src/server/resolve-home-segment-data.js +20 -0
- package/dist/src/server/returns.d.ts.map +1 -0
- package/dist/src/server/shoppable-looks.d.ts +17 -0
- package/dist/src/server/shoppable-looks.d.ts.map +1 -0
- package/dist/src/server/shoppable-looks.js +136 -0
- package/dist/src/server/swaps.d.ts.map +1 -0
- package/dist/src/server/variants.d.ts.map +1 -0
- package/dist/src/server/wishlist.d.ts.map +1 -0
- package/dist/src/util/get-locale-header.d.ts.map +1 -0
- package/dist/src/util/medusa-error.d.ts.map +1 -0
- package/dist/src/util/revalidate-cart.d.ts.map +1 -0
- package/dist/src/util/sort-products.d.ts.map +1 -0
- package/dist/src/util/store-client.d.ts.map +1 -0
- package/homepage-config.example.json +209 -0
- package/homepage-sections.example.json +55 -0
- package/package.json +121 -2
- package/segment-data/default-page-input.json +191 -0
- package/src/server/config-merge.ts +31 -0
- package/src/server/dynamic-config.ts +620 -508
- package/src/server/home-sections/about-brand.ts +24 -0
- package/src/server/home-sections/baptism-picks.ts +46 -0
- package/src/server/home-sections/baptism.ts +64 -0
- package/src/server/home-sections/blog-posts.ts +23 -0
- package/src/server/home-sections/brand-marquee.ts +17 -0
- package/src/server/home-sections/brand-pillars.ts +23 -0
- package/src/server/home-sections/category-pills.ts +32 -0
- package/src/server/home-sections/celebrity-trust.ts +42 -0
- package/src/server/home-sections/features.ts +23 -0
- package/src/server/home-sections/hero.ts +24 -0
- package/src/server/home-sections/instagram-posts.ts +23 -0
- package/src/server/home-sections/loved-by-moms.ts +34 -0
- package/src/server/home-sections/luxe-favourites.ts +30 -0
- package/src/server/home-sections/new-arrivals-classic.ts +21 -0
- package/src/server/home-sections/new-arrivals.ts +47 -0
- package/src/server/home-sections/promo-announcements.ts +17 -0
- package/src/server/home-sections/promo-countdown.ts +15 -0
- package/src/server/home-sections/shared.ts +44 -0
- package/src/server/home-sections/shop-by-age.ts +26 -0
- package/src/server/home-sections/shop-by-category.ts +28 -0
- package/src/server/home-sections/testimonials.ts +23 -0
- package/src/server/home-sections/theme-dresses.ts +49 -0
- package/src/server/home-sections/video-stories.ts +23 -0
- package/src/server/home-sections/why-choose-us.ts +23 -0
- package/src/server/home.ts +68 -54
- package/src/server/homepage-config.types.ts +335 -0
- package/src/server/homepage-section-defaults.ts +41 -2
- package/src/server/index.ts +2 -0
- package/src/server/page-input.ts +64 -0
- package/src/server/resolve-home-segment-data.ts +63 -0
- package/src/server/shoppable-looks.ts +178 -0
- package/dist/config.d.ts.map +0 -1
- package/dist/cookies.d.ts.map +0 -1
- package/dist/edge.d.ts.map +0 -1
- package/dist/middleware.d.ts.map +0 -1
- package/dist/server/cart.d.ts.map +0 -1
- package/dist/server/categories.d.ts.map +0 -1
- package/dist/server/collections.d.ts.map +0 -1
- package/dist/server/contact.d.ts.map +0 -1
- package/dist/server/customer-registration.d.ts.map +0 -1
- package/dist/server/customer.d.ts.map +0 -1
- package/dist/server/dynamic-config.d.ts +0 -202
- package/dist/server/dynamic-config.d.ts.map +0 -1
- package/dist/server/dynamic-config.js +0 -675
- package/dist/server/fulfillment.d.ts.map +0 -1
- package/dist/server/guest.d.ts.map +0 -1
- package/dist/server/home.d.ts +0 -42
- package/dist/server/home.d.ts.map +0 -1
- package/dist/server/homepage-section-defaults.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/locale-actions.d.ts.map +0 -1
- package/dist/server/locales.d.ts.map +0 -1
- package/dist/server/notifications.d.ts.map +0 -1
- package/dist/server/onboarding.d.ts.map +0 -1
- package/dist/server/orders.d.ts.map +0 -1
- package/dist/server/payment-details.d.ts.map +0 -1
- package/dist/server/payment.d.ts.map +0 -1
- package/dist/server/pincode.d.ts.map +0 -1
- package/dist/server/products.d.ts.map +0 -1
- package/dist/server/regions.d.ts.map +0 -1
- package/dist/server/returns.d.ts.map +0 -1
- package/dist/server/swaps.d.ts.map +0 -1
- package/dist/server/variants.d.ts.map +0 -1
- package/dist/server/wishlist.d.ts.map +0 -1
- package/dist/util/get-locale-header.d.ts.map +0 -1
- package/dist/util/medusa-error.d.ts.map +0 -1
- package/dist/util/revalidate-cart.d.ts.map +0 -1
- package/dist/util/sort-products.d.ts.map +0 -1
- package/dist/util/store-client.d.ts.map +0 -1
- /package/dist/{config.d.ts → src/config.d.ts} +0 -0
- /package/dist/{config.js → src/config.js} +0 -0
- /package/dist/{cookies.d.ts → src/cookies.d.ts} +0 -0
- /package/dist/{cookies.js → src/cookies.js} +0 -0
- /package/dist/{edge.d.ts → src/edge.d.ts} +0 -0
- /package/dist/{edge.js → src/edge.js} +0 -0
- /package/dist/{middleware.d.ts → src/middleware.d.ts} +0 -0
- /package/dist/{middleware.js → src/middleware.js} +0 -0
- /package/dist/{server → src/server}/cart.d.ts +0 -0
- /package/dist/{server → src/server}/cart.js +0 -0
- /package/dist/{server → src/server}/categories.d.ts +0 -0
- /package/dist/{server → src/server}/categories.js +0 -0
- /package/dist/{server → src/server}/collections.d.ts +0 -0
- /package/dist/{server → src/server}/collections.js +0 -0
- /package/dist/{server → src/server}/contact.d.ts +0 -0
- /package/dist/{server → src/server}/contact.js +0 -0
- /package/dist/{server → src/server}/customer-registration.d.ts +0 -0
- /package/dist/{server → src/server}/customer-registration.js +0 -0
- /package/dist/{server → src/server}/customer.d.ts +0 -0
- /package/dist/{server → src/server}/customer.js +0 -0
- /package/dist/{server → src/server}/fulfillment.d.ts +0 -0
- /package/dist/{server → src/server}/fulfillment.js +0 -0
- /package/dist/{server → src/server}/guest.d.ts +0 -0
- /package/dist/{server → src/server}/guest.js +0 -0
- /package/dist/{server → src/server}/locale-actions.d.ts +0 -0
- /package/dist/{server → src/server}/locale-actions.js +0 -0
- /package/dist/{server → src/server}/locales.d.ts +0 -0
- /package/dist/{server → src/server}/locales.js +0 -0
- /package/dist/{server → src/server}/notifications.d.ts +0 -0
- /package/dist/{server → src/server}/notifications.js +0 -0
- /package/dist/{server → src/server}/onboarding.d.ts +0 -0
- /package/dist/{server → src/server}/onboarding.js +0 -0
- /package/dist/{server → src/server}/orders.d.ts +0 -0
- /package/dist/{server → src/server}/orders.js +0 -0
- /package/dist/{server → src/server}/payment-details.d.ts +0 -0
- /package/dist/{server → src/server}/payment-details.js +0 -0
- /package/dist/{server → src/server}/payment.d.ts +0 -0
- /package/dist/{server → src/server}/payment.js +0 -0
- /package/dist/{server → src/server}/pincode.d.ts +0 -0
- /package/dist/{server → src/server}/pincode.js +0 -0
- /package/dist/{server → src/server}/products.d.ts +0 -0
- /package/dist/{server → src/server}/products.js +0 -0
- /package/dist/{server → src/server}/regions.d.ts +0 -0
- /package/dist/{server → src/server}/regions.js +0 -0
- /package/dist/{server → src/server}/returns.d.ts +0 -0
- /package/dist/{server → src/server}/returns.js +0 -0
- /package/dist/{server → src/server}/swaps.d.ts +0 -0
- /package/dist/{server → src/server}/swaps.js +0 -0
- /package/dist/{server → src/server}/variants.d.ts +0 -0
- /package/dist/{server → src/server}/variants.js +0 -0
- /package/dist/{server → src/server}/wishlist.d.ts +0 -0
- /package/dist/{server → src/server}/wishlist.js +0 -0
- /package/dist/{util → src/util}/get-locale-header.d.ts +0 -0
- /package/dist/{util → src/util}/get-locale-header.js +0 -0
- /package/dist/{util → src/util}/medusa-error.d.ts +0 -0
- /package/dist/{util → src/util}/medusa-error.js +0 -0
- /package/dist/{util → src/util}/revalidate-cart.d.ts +0 -0
- /package/dist/{util → src/util}/revalidate-cart.js +0 -0
- /package/dist/{util → src/util}/sort-products.d.ts +0 -0
- /package/dist/{util → src/util}/sort-products.js +0 -0
- /package/dist/{util → src/util}/store-client.d.ts +0 -0
- /package/dist/{util → src/util}/store-client.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"
|
|
1
|
+
import "server-only"
|
|
2
2
|
|
|
3
3
|
import { cache } from "react"
|
|
4
4
|
import { getAuthHeaders } from "../cookies"
|
|
@@ -7,74 +7,22 @@ import {
|
|
|
7
7
|
HOME_SECTION_COPY_ID_LIST,
|
|
8
8
|
type DefaultHomeSectionId,
|
|
9
9
|
} from "./homepage-section-defaults"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"app-banner"?: {
|
|
27
|
-
"app-banner-image"?: string
|
|
28
|
-
"app-banner-title"?: string
|
|
29
|
-
"app-banner-subtitle"?: string
|
|
30
|
-
"app-banner-description"?: string
|
|
31
|
-
"app-banner-button-name"?: string
|
|
32
|
-
"app-banner-button-link"?: string
|
|
33
|
-
"app-banner-link"?: string
|
|
34
|
-
}
|
|
35
|
-
}>
|
|
36
|
-
"website-description"?: string
|
|
37
|
-
"website_description"?: string
|
|
38
|
-
"why-choose-us-title"?: string
|
|
39
|
-
"why-choose-us-features"?: Array<{
|
|
40
|
-
feature?: {
|
|
41
|
-
"feature-name"?: string
|
|
42
|
-
"feature-icon"?: string
|
|
43
|
-
}
|
|
44
|
-
// Handle potential direct structure if any
|
|
45
|
-
"feature-name"?: string
|
|
46
|
-
"feature-icon"?: string
|
|
47
|
-
}>
|
|
48
|
-
"contact-us"?: {
|
|
49
|
-
"contact-phone"?: string
|
|
50
|
-
"contact-email"?: string
|
|
51
|
-
"contact-address"?: string
|
|
52
|
-
}
|
|
53
|
-
"social-links"?: Array<{
|
|
54
|
-
"social-link"?: {
|
|
55
|
-
"social-platform-name"?: string
|
|
56
|
-
"social-platform-icon"?: string
|
|
57
|
-
"social-platform-url"?: string
|
|
58
|
-
}
|
|
59
|
-
// Handle potential direct structure if any
|
|
60
|
-
"social-platform-name"?: string
|
|
61
|
-
"social-platform-icon"?: string
|
|
62
|
-
"social-platform-url"?: string
|
|
63
|
-
}>
|
|
64
|
-
"promo-bar"?: {
|
|
65
|
-
"promo-text"?: string
|
|
66
|
-
"promo-code"?: string
|
|
67
|
-
"promo-value"?: string
|
|
68
|
-
"promo-active"?: boolean
|
|
69
|
-
}
|
|
70
|
-
/** Central homepage section copy (Option 1). Keys: camelCase or kebab-case section ids. */
|
|
71
|
-
sections?: Record<string, unknown>
|
|
72
|
-
// Allow for other properties
|
|
73
|
-
[key: string]: any
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const getDynamicConfig = cache(async (): Promise<DynamicConfig | null> => {
|
|
10
|
+
import type { DynamicConfigResponse, HomepageConfig } from "./homepage-config.types"
|
|
11
|
+
import { mergeSectionBlock } from "./config-merge"
|
|
12
|
+
import { mergePageInputWithDefaults, resolvePageInput } from "./page-input"
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_PAGE_INPUT,
|
|
16
|
+
mergePageInputWithDefaults,
|
|
17
|
+
resolvePageInput,
|
|
18
|
+
homepageConfigFromDynamicResponse,
|
|
19
|
+
type StorefrontPageInput,
|
|
20
|
+
} from "./page-input"
|
|
21
|
+
|
|
22
|
+
/** @deprecated Use `DynamicConfigResponse` from `medusa-storefront-data/homepage-config-schema`. */
|
|
23
|
+
export type DynamicConfig = DynamicConfigResponse
|
|
24
|
+
|
|
25
|
+
export const getDynamicConfig = cache(async (): Promise<DynamicConfigResponse | null> => {
|
|
78
26
|
try {
|
|
79
27
|
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
80
28
|
const baseUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
@@ -123,16 +71,17 @@ export const getDynamicConfig = cache(async (): Promise<DynamicConfig | null> =>
|
|
|
123
71
|
}
|
|
124
72
|
})
|
|
125
73
|
|
|
126
|
-
export
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
} catch (error) {
|
|
74
|
+
export function getLogoFromPageInput(
|
|
75
|
+
pageInput?: HomepageConfig | null
|
|
76
|
+
): string | null {
|
|
77
|
+
const config = resolvePageInput(pageInput)
|
|
78
|
+
return config.logo || null
|
|
79
|
+
}
|
|
133
80
|
|
|
134
|
-
|
|
135
|
-
|
|
81
|
+
/** @deprecated Fetch in main project; pass `pageInput` into layout/pages instead. */
|
|
82
|
+
export const getLogoFromConfig = async (): Promise<string | null> => {
|
|
83
|
+
const config = await getDynamicConfig()
|
|
84
|
+
return getLogoFromPageInput(config?.["homepage-config"] ?? null)
|
|
136
85
|
}
|
|
137
86
|
|
|
138
87
|
export const getBannerImageFromConfig = async (): Promise<string | null> => {
|
|
@@ -176,68 +125,67 @@ export const getMobileBannerFromConfig = async (): Promise<{ image: string | nul
|
|
|
176
125
|
}
|
|
177
126
|
}
|
|
178
127
|
|
|
179
|
-
export
|
|
180
|
-
image?: string
|
|
181
|
-
title?: string
|
|
182
|
-
subtitle?: string
|
|
183
|
-
description?: string
|
|
184
|
-
buttonName?: string
|
|
185
|
-
buttonLink?: string
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
} catch (error) {
|
|
206
|
-
|
|
207
|
-
return null
|
|
128
|
+
export type HomepageBanner = {
|
|
129
|
+
image?: string
|
|
130
|
+
title?: string
|
|
131
|
+
subtitle?: string
|
|
132
|
+
description?: string
|
|
133
|
+
buttonName?: string
|
|
134
|
+
buttonLink?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getHomeBannersFromPageInput(
|
|
138
|
+
pageInput?: HomepageConfig | null
|
|
139
|
+
): HomepageBanner[] | null {
|
|
140
|
+
const config = resolvePageInput(pageInput)
|
|
141
|
+
const bannerArray = config["homepage-banner-array"]
|
|
142
|
+
if (bannerArray && bannerArray.length > 0) {
|
|
143
|
+
return bannerArray.map((item) => {
|
|
144
|
+
const banner = item["homepage-banner"]
|
|
145
|
+
return {
|
|
146
|
+
image: banner?.["homepage-banner-image"],
|
|
147
|
+
title: normalizeCmsText(banner?.["homepage-banner-title"]),
|
|
148
|
+
subtitle: normalizeCmsText(banner?.["homepage-banner-subtitle"]),
|
|
149
|
+
description: normalizeCmsText(banner?.["homepage-banner-description"]),
|
|
150
|
+
buttonName: normalizeBannerButtonName(banner?.["homepage-banner-button-name"]),
|
|
151
|
+
buttonLink: banner?.["homepage-banner-button-link"],
|
|
152
|
+
}
|
|
153
|
+
})
|
|
208
154
|
}
|
|
155
|
+
return null
|
|
209
156
|
}
|
|
210
157
|
|
|
211
|
-
export
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
158
|
+
export function getAppBannersFromPageInput(
|
|
159
|
+
pageInput?: HomepageConfig | null
|
|
160
|
+
): HomepageBanner[] | null {
|
|
161
|
+
const config = resolvePageInput(pageInput)
|
|
162
|
+
const bannerArray = config["app-banner-array"]
|
|
163
|
+
if (bannerArray && bannerArray.length > 0) {
|
|
164
|
+
return bannerArray.map((item) => {
|
|
165
|
+
const banner = item["app-banner"]
|
|
166
|
+
return {
|
|
167
|
+
image: banner?.["app-banner-image"],
|
|
168
|
+
title: normalizeCmsText(banner?.["app-banner-title"]),
|
|
169
|
+
subtitle: normalizeCmsText(banner?.["app-banner-subtitle"]),
|
|
170
|
+
description: normalizeCmsText(banner?.["app-banner-description"]),
|
|
171
|
+
buttonName: normalizeBannerButtonName(banner?.["app-banner-button-name"]),
|
|
172
|
+
buttonLink: banner?.["app-banner-button-link"],
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
222
178
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
title: normalizeCmsText(banner?.["app-banner-title"]),
|
|
229
|
-
subtitle: normalizeCmsText(banner?.["app-banner-subtitle"]),
|
|
230
|
-
description: normalizeCmsText(banner?.["app-banner-description"]),
|
|
231
|
-
buttonName: normalizeBannerButtonName(banner?.["app-banner-button-name"]),
|
|
232
|
-
buttonLink: banner?.["app-banner-button-link"],
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
return null
|
|
237
|
-
} catch (error) {
|
|
179
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
180
|
+
export const getHomeBannersFromConfig = async (): Promise<HomepageBanner[] | null> => {
|
|
181
|
+
const config = await getDynamicConfig()
|
|
182
|
+
return getHomeBannersFromPageInput(config?.["homepage-config"] ?? null)
|
|
183
|
+
}
|
|
238
184
|
|
|
239
|
-
|
|
240
|
-
|
|
185
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
186
|
+
export const getAppBannersFromConfig = async (): Promise<HomepageBanner[] | null> => {
|
|
187
|
+
const config = await getDynamicConfig()
|
|
188
|
+
return getAppBannersFromPageInput(config?.["homepage-config"] ?? null)
|
|
241
189
|
}
|
|
242
190
|
|
|
243
191
|
// Legacy function for backward compatibility
|
|
@@ -246,41 +194,42 @@ export const getMobileBannerImageFromConfig = async (): Promise<string | null> =
|
|
|
246
194
|
return image
|
|
247
195
|
}
|
|
248
196
|
|
|
249
|
-
export
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return desc || null
|
|
257
|
-
} catch (error) {
|
|
197
|
+
export function getWebsiteDescriptionFromPageInput(
|
|
198
|
+
pageInput?: HomepageConfig | null
|
|
199
|
+
): string | null {
|
|
200
|
+
const config = resolvePageInput(pageInput)
|
|
201
|
+
const desc = config["website-description"] || config["website_description"]
|
|
202
|
+
return desc || null
|
|
203
|
+
}
|
|
258
204
|
|
|
259
|
-
|
|
260
|
-
|
|
205
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
206
|
+
export const getWebsiteDescriptionFromConfig = async (): Promise<string | null> => {
|
|
207
|
+
const config = await getDynamicConfig()
|
|
208
|
+
return getWebsiteDescriptionFromPageInput(config?.["homepage-config"] ?? null)
|
|
261
209
|
}
|
|
262
210
|
|
|
263
211
|
/** Why choose us — defaults + `sections.whyChooseUs` override. */
|
|
212
|
+
export function getFeaturesFromPageInput(
|
|
213
|
+
pageInput?: HomepageConfig | null
|
|
214
|
+
): {
|
|
215
|
+
title: string
|
|
216
|
+
features: Array<{ name: string; icon: string }>
|
|
217
|
+
} {
|
|
218
|
+
const block = getMergedSectionBlock("whyChooseUs", pageInput)
|
|
219
|
+
const copy = parseSectionCopy(block)
|
|
220
|
+
const title = copy.title ?? copy.name ?? ""
|
|
221
|
+
const featuresRaw = block.features ?? block.items ?? []
|
|
222
|
+
const features = parseFeatureIconList(featuresRaw)
|
|
223
|
+
return { title, features }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
264
227
|
export const getFeaturesFromConfig = async (): Promise<{
|
|
265
228
|
title: string
|
|
266
229
|
features: Array<{ name: string; icon: string }>
|
|
267
230
|
}> => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const copy = parseSectionCopy(block)
|
|
271
|
-
const title = copy.title ?? copy.name ?? ""
|
|
272
|
-
const featuresRaw = block.features ?? block.items ?? []
|
|
273
|
-
const features = parseFeatureIconList(featuresRaw)
|
|
274
|
-
|
|
275
|
-
return { title, features }
|
|
276
|
-
} catch {
|
|
277
|
-
const block = DEFAULT_HOMEPAGE_SECTIONS.whyChooseUs
|
|
278
|
-
const copy = parseSectionCopy(block)
|
|
279
|
-
return {
|
|
280
|
-
title: copy.title ?? copy.name ?? "",
|
|
281
|
-
features: parseFeatureIconList(block.features ?? []),
|
|
282
|
-
}
|
|
283
|
-
}
|
|
231
|
+
const config = await getDynamicConfig()
|
|
232
|
+
return getFeaturesFromPageInput(config?.["homepage-config"] ?? null)
|
|
284
233
|
}
|
|
285
234
|
|
|
286
235
|
function parseFeatureIconList(raw: unknown): Array<{ name: string; icon: string }> {
|
|
@@ -318,6 +267,9 @@ export type SectionCopy = {
|
|
|
318
267
|
description: string | null
|
|
319
268
|
subtitle: string | null
|
|
320
269
|
eyebrow: string | null
|
|
270
|
+
/** Layout variant, e.g. new arrivals `grid` | `classic`. */
|
|
271
|
+
variant: string | null
|
|
272
|
+
layout: string | null
|
|
321
273
|
}
|
|
322
274
|
|
|
323
275
|
const EMPTY_SECTION_COPY: SectionCopy = {
|
|
@@ -327,6 +279,8 @@ const EMPTY_SECTION_COPY: SectionCopy = {
|
|
|
327
279
|
description: null,
|
|
328
280
|
subtitle: null,
|
|
329
281
|
eyebrow: null,
|
|
282
|
+
variant: null,
|
|
283
|
+
layout: null,
|
|
330
284
|
}
|
|
331
285
|
|
|
332
286
|
export function parseSectionCopy(block: unknown): SectionCopy {
|
|
@@ -339,6 +293,8 @@ export function parseSectionCopy(block: unknown): SectionCopy {
|
|
|
339
293
|
description: normalizeCmsText(row.description) ?? null,
|
|
340
294
|
subtitle: normalizeCmsText(row.subtitle) ?? null,
|
|
341
295
|
eyebrow: normalizeCmsText(row.eyebrow) ?? null,
|
|
296
|
+
variant: normalizeCmsText(row.variant) ?? null,
|
|
297
|
+
layout: normalizeCmsText(row.layout) ?? null,
|
|
342
298
|
}
|
|
343
299
|
}
|
|
344
300
|
|
|
@@ -355,9 +311,11 @@ function sectionIdFromConfigKey(key: string): HomeSectionCopyId | null {
|
|
|
355
311
|
return HOME_SECTION_COPY_IDS.has(id) ? id : null
|
|
356
312
|
}
|
|
357
313
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
314
|
+
function getHomepageSectionsRoot(
|
|
315
|
+
pageInput?: HomepageConfig | null
|
|
316
|
+
): Record<string, unknown> {
|
|
317
|
+
const config = resolvePageInput(pageInput)
|
|
318
|
+
const root = config.sections
|
|
361
319
|
return root && typeof root === "object"
|
|
362
320
|
? (root as Record<string, unknown>)
|
|
363
321
|
: {}
|
|
@@ -379,42 +337,13 @@ function findSectionBlock(
|
|
|
379
337
|
return null
|
|
380
338
|
}
|
|
381
339
|
|
|
382
|
-
|
|
383
|
-
export function mergeSectionBlock(
|
|
384
|
-
defaults: Record<string, unknown>,
|
|
385
|
-
override: Record<string, unknown> | null
|
|
386
|
-
): Record<string, unknown> {
|
|
387
|
-
if (!override) return { ...defaults }
|
|
388
|
-
const merged = { ...defaults }
|
|
389
|
-
for (const [key, value] of Object.entries(override)) {
|
|
390
|
-
if (value === undefined || value === null) continue
|
|
391
|
-
if (Array.isArray(value)) {
|
|
392
|
-
if (value.length > 0) merged[key] = value
|
|
393
|
-
continue
|
|
394
|
-
}
|
|
395
|
-
if (typeof value === "string") {
|
|
396
|
-
if (value.trim()) merged[key] = value.trim()
|
|
397
|
-
continue
|
|
398
|
-
}
|
|
399
|
-
if (typeof value === "boolean" || typeof value === "number") {
|
|
400
|
-
merged[key] = value
|
|
401
|
-
continue
|
|
402
|
-
}
|
|
403
|
-
if (typeof value === "object") {
|
|
404
|
-
const base =
|
|
405
|
-
merged[key] && typeof merged[key] === "object" && !Array.isArray(merged[key])
|
|
406
|
-
? (merged[key] as Record<string, unknown>)
|
|
407
|
-
: {}
|
|
408
|
-
merged[key] = mergeSectionBlock(base, value as Record<string, unknown>)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return merged
|
|
412
|
-
}
|
|
340
|
+
export { mergeSectionBlock } from "./config-merge"
|
|
413
341
|
|
|
414
|
-
|
|
415
|
-
id: HomeSectionCopyId
|
|
416
|
-
|
|
417
|
-
|
|
342
|
+
export function getMergedSectionBlock(
|
|
343
|
+
id: HomeSectionCopyId,
|
|
344
|
+
pageInput?: HomepageConfig | null
|
|
345
|
+
): Record<string, unknown> {
|
|
346
|
+
const root = getHomepageSectionsRoot(pageInput)
|
|
418
347
|
const defaults = DEFAULT_HOMEPAGE_SECTIONS[id] ?? {}
|
|
419
348
|
const override = findSectionBlock(root, id)
|
|
420
349
|
return mergeSectionBlock(
|
|
@@ -433,6 +362,8 @@ function mergeSectionCopy(base: SectionCopy, patch: SectionCopy): SectionCopy {
|
|
|
433
362
|
description: pick(patch.description, base.description),
|
|
434
363
|
subtitle: pick(patch.subtitle, base.subtitle),
|
|
435
364
|
eyebrow: pick(patch.eyebrow, base.eyebrow),
|
|
365
|
+
variant: pick(patch.variant ?? patch.layout, base.variant ?? base.layout),
|
|
366
|
+
layout: pick(patch.layout ?? patch.variant, base.layout ?? base.variant),
|
|
436
367
|
}
|
|
437
368
|
}
|
|
438
369
|
|
|
@@ -456,21 +387,21 @@ function parseAnnouncementMessages(block: Record<string, unknown> | null): strin
|
|
|
456
387
|
/**
|
|
457
388
|
* Merged section copy: package defaults + `homepage-config.sections` overrides.
|
|
458
389
|
*/
|
|
459
|
-
export
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return copy
|
|
467
|
-
} catch {
|
|
468
|
-
const fallback = {} as HomeSectionsCopy
|
|
469
|
-
for (const id of HOME_SECTION_COPY_ID_LIST) {
|
|
470
|
-
fallback[id] = parseSectionCopy(DEFAULT_HOMEPAGE_SECTIONS[id])
|
|
471
|
-
}
|
|
472
|
-
return fallback
|
|
390
|
+
export function getHomeSectionsCopyFromPageInput(
|
|
391
|
+
pageInput?: HomepageConfig | null
|
|
392
|
+
): HomeSectionsCopy {
|
|
393
|
+
const copy = {} as HomeSectionsCopy
|
|
394
|
+
for (const id of HOME_SECTION_COPY_ID_LIST) {
|
|
395
|
+
const block = getMergedSectionBlock(id, pageInput)
|
|
396
|
+
copy[id] = parseSectionCopy(block)
|
|
473
397
|
}
|
|
398
|
+
return copy
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @deprecated Pass `pageInput` from main project instead of fetching here. */
|
|
402
|
+
export const getHomeSectionsCopyFromConfig = async (): Promise<HomeSectionsCopy> => {
|
|
403
|
+
const config = await getDynamicConfig()
|
|
404
|
+
return getHomeSectionsCopyFromPageInput(config?.["homepage-config"] ?? null)
|
|
474
405
|
}
|
|
475
406
|
|
|
476
407
|
export type TrustFeaturesConfig = {
|
|
@@ -480,25 +411,22 @@ export type TrustFeaturesConfig = {
|
|
|
480
411
|
}
|
|
481
412
|
|
|
482
413
|
/** Trust badge row — defaults + `sections.features` override. */
|
|
414
|
+
export function getTrustFeaturesFromPageInput(
|
|
415
|
+
pageInput?: HomepageConfig | null
|
|
416
|
+
): TrustFeaturesConfig {
|
|
417
|
+
const block = getMergedSectionBlock("features", pageInput)
|
|
418
|
+
const copy = parseSectionCopy(block)
|
|
419
|
+
const title = copy.title ?? copy.name
|
|
420
|
+
const description = copy.description ?? copy.text
|
|
421
|
+
const rawList = block.features ?? block.items ?? []
|
|
422
|
+
const features = parseFeatureIconList(rawList)
|
|
423
|
+
return { title: title ?? null, description: description ?? null, features }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
483
427
|
export const getTrustFeaturesFromConfig = async (): Promise<TrustFeaturesConfig> => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const copy = parseSectionCopy(block)
|
|
487
|
-
const title = copy.title ?? copy.name
|
|
488
|
-
const description = copy.description ?? copy.text
|
|
489
|
-
const rawList = block.features ?? block.items ?? []
|
|
490
|
-
const features = parseFeatureIconList(rawList)
|
|
491
|
-
|
|
492
|
-
return { title: title ?? null, description: description ?? null, features }
|
|
493
|
-
} catch {
|
|
494
|
-
const block = DEFAULT_HOMEPAGE_SECTIONS.features
|
|
495
|
-
const copy = parseSectionCopy(block)
|
|
496
|
-
return {
|
|
497
|
-
title: copy.title,
|
|
498
|
-
description: copy.description,
|
|
499
|
-
features: parseFeatureIconList(block.features ?? []),
|
|
500
|
-
}
|
|
501
|
-
}
|
|
428
|
+
const config = await getDynamicConfig()
|
|
429
|
+
return getTrustFeaturesFromPageInput(config?.["homepage-config"] ?? null)
|
|
502
430
|
}
|
|
503
431
|
|
|
504
432
|
function normalizeBannerButtonName(value: unknown): string | undefined {
|
|
@@ -514,29 +442,27 @@ function normalizeConfigEmail(value: unknown): string | undefined {
|
|
|
514
442
|
return EMAIL_PATTERN.test(trimmed) ? trimmed : undefined
|
|
515
443
|
}
|
|
516
444
|
|
|
517
|
-
export
|
|
518
|
-
try {
|
|
519
|
-
const config = await getDynamicConfig()
|
|
520
|
-
const contactConfig = config?.["homepage-config"]?.["contact-us"]
|
|
521
|
-
|
|
522
|
-
if (!contactConfig) {
|
|
445
|
+
export type ContactInfo = { phone?: string; email?: string; address?: string }
|
|
523
446
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
} catch (error) {
|
|
535
|
-
|
|
536
|
-
return null
|
|
447
|
+
export function getContactInfoFromPageInput(
|
|
448
|
+
pageInput?: HomepageConfig | null
|
|
449
|
+
): ContactInfo | null {
|
|
450
|
+
const config = resolvePageInput(pageInput)
|
|
451
|
+
const contactConfig = config["contact-us"]
|
|
452
|
+
if (!contactConfig) return null
|
|
453
|
+
return {
|
|
454
|
+
phone: contactConfig["contact-phone"],
|
|
455
|
+
email: normalizeConfigEmail(contactConfig["contact-email"]),
|
|
456
|
+
address: contactConfig["contact-address"],
|
|
537
457
|
}
|
|
538
458
|
}
|
|
539
459
|
|
|
460
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
461
|
+
export const getContactInfoFromConfig = async (): Promise<ContactInfo | null> => {
|
|
462
|
+
const config = await getDynamicConfig()
|
|
463
|
+
return getContactInfoFromPageInput(config?.["homepage-config"] ?? null)
|
|
464
|
+
}
|
|
465
|
+
|
|
540
466
|
function normalizeVideoStoryUrl(entry: unknown): string | null {
|
|
541
467
|
if (typeof entry === "string" && entry.trim()) return entry.trim()
|
|
542
468
|
if (!entry || typeof entry !== "object") return null
|
|
@@ -557,142 +483,210 @@ function normalizeVideoStoryUrl(entry: unknown): string | null {
|
|
|
557
483
|
/**
|
|
558
484
|
* Homepage video stories/reviews carousel (`homepage-config.video-stories`).
|
|
559
485
|
*/
|
|
486
|
+
export function getVideoStoriesFromPageInput(
|
|
487
|
+
pageInput?: HomepageConfig | null
|
|
488
|
+
): { title: string; videoUrls: string[] } {
|
|
489
|
+
const homepageConfig = resolvePageInput(pageInput)
|
|
490
|
+
const block = getMergedSectionBlock("videoStories", pageInput)
|
|
491
|
+
const copy = parseSectionCopy(block)
|
|
492
|
+
const title = copy.title ?? copy.name ?? ""
|
|
493
|
+
const raw =
|
|
494
|
+
block["video-urls"] ??
|
|
495
|
+
block.videoUrls ??
|
|
496
|
+
block.videos ??
|
|
497
|
+
block["video-stories"] ??
|
|
498
|
+
homepageConfig["video-stories"] ??
|
|
499
|
+
homepageConfig["video-stories-array"] ??
|
|
500
|
+
homepageConfig["video_stories"] ??
|
|
501
|
+
[]
|
|
502
|
+
const list = Array.isArray(raw) ? raw : [raw]
|
|
503
|
+
const videoUrls = list
|
|
504
|
+
.map(normalizeVideoStoryUrl)
|
|
505
|
+
.filter((url): url is string => Boolean(url))
|
|
506
|
+
return { title, videoUrls }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
560
510
|
export const getVideoStoriesFromConfig = async (): Promise<{
|
|
561
511
|
title: string
|
|
562
512
|
videoUrls: string[]
|
|
563
513
|
} | null> => {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
514
|
+
const config = await getDynamicConfig()
|
|
515
|
+
return getVideoStoriesFromPageInput(config?.["homepage-config"] ?? null)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export type TestimonialItem = {
|
|
519
|
+
id: string
|
|
520
|
+
text: string
|
|
521
|
+
name: string
|
|
522
|
+
rating?: number
|
|
523
|
+
avatar?: string
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function parseTestimonialRow(
|
|
527
|
+
item: unknown,
|
|
528
|
+
index: number
|
|
529
|
+
): TestimonialItem | null {
|
|
530
|
+
if (!item || typeof item !== "object") return null
|
|
531
|
+
const row = (item as Record<string, unknown>).testimonial ??
|
|
532
|
+
(item as Record<string, unknown>).review ??
|
|
533
|
+
item
|
|
534
|
+
const r = row as Record<string, unknown>
|
|
535
|
+
const text = String(
|
|
536
|
+
r.text ?? r["review-description"] ?? r.description ?? r.quote ?? ""
|
|
537
|
+
).trim()
|
|
538
|
+
const name = String(
|
|
539
|
+
r.name ?? r["review-user-name"] ?? r.author ?? r["user-name"] ?? ""
|
|
540
|
+
).trim()
|
|
541
|
+
if (!text || !name) return null
|
|
542
|
+
const ratingRaw = r.rating ?? r["review-rating"]
|
|
543
|
+
const rating =
|
|
544
|
+
ratingRaw != null && ratingRaw !== ""
|
|
545
|
+
? parseFloat(String(ratingRaw))
|
|
546
|
+
: undefined
|
|
547
|
+
return {
|
|
548
|
+
id: String(r.id ?? `review-${index}`),
|
|
549
|
+
text,
|
|
550
|
+
name,
|
|
551
|
+
rating: Number.isFinite(rating) ? rating : undefined,
|
|
552
|
+
avatar: (r.avatar ?? r["review-profile-image"] ?? r.image) as string | undefined,
|
|
596
553
|
}
|
|
597
554
|
}
|
|
598
555
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
556
|
+
function parseTestimonialsFromBlock(block: Record<string, unknown>): TestimonialItem[] {
|
|
557
|
+
const raw =
|
|
558
|
+
block.items ??
|
|
559
|
+
block.testimonials ??
|
|
560
|
+
block.reviews ??
|
|
561
|
+
block["testimonial-items"] ??
|
|
562
|
+
[]
|
|
563
|
+
if (!Array.isArray(raw)) return []
|
|
564
|
+
return raw
|
|
565
|
+
.map((item, index) => parseTestimonialRow(item, index))
|
|
566
|
+
.filter((t): t is TestimonialItem => t !== null)
|
|
567
|
+
}
|
|
610
568
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
rating: parseFloat(String(review?.["review-rating"] ?? "5")),
|
|
620
|
-
avatar: review?.["review-profile-image"] as string | undefined,
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
).filter((t) => t.text && t.name)
|
|
569
|
+
function parseRatingsFromHomepage(
|
|
570
|
+
ratings: unknown
|
|
571
|
+
): TestimonialItem[] {
|
|
572
|
+
if (!Array.isArray(ratings)) return []
|
|
573
|
+
return ratings
|
|
574
|
+
.map((item, index) => parseTestimonialRow(item, index))
|
|
575
|
+
.filter((t): t is TestimonialItem => t !== null)
|
|
576
|
+
}
|
|
624
577
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
578
|
+
export function getTestimonialsFromPageInput(
|
|
579
|
+
pageInput?: HomepageConfig | null
|
|
580
|
+
): {
|
|
581
|
+
title: string
|
|
582
|
+
subtitle: string | null
|
|
583
|
+
testimonials: TestimonialItem[]
|
|
584
|
+
} {
|
|
585
|
+
const homepageConfig = resolvePageInput(pageInput)
|
|
586
|
+
const block = getMergedSectionBlock("testimonials", pageInput)
|
|
587
|
+
const copy = parseSectionCopy(block)
|
|
588
|
+
const title = copy.title ?? copy.name ?? ""
|
|
589
|
+
const subtitle = (copy.description ?? copy.text ?? null) as string | null
|
|
590
|
+
let testimonials = parseTestimonialsFromBlock(block)
|
|
591
|
+
if (testimonials.length === 0) {
|
|
592
|
+
testimonials = parseRatingsFromHomepage(homepageConfig["ratings"])
|
|
593
|
+
}
|
|
594
|
+
if (testimonials.length === 0) {
|
|
595
|
+
testimonials = parseTestimonialsFromBlock(
|
|
596
|
+
DEFAULT_HOMEPAGE_SECTIONS.testimonials as Record<string, unknown>
|
|
597
|
+
)
|
|
630
598
|
}
|
|
599
|
+
return { title, subtitle, testimonials }
|
|
631
600
|
}
|
|
632
601
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
602
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
603
|
+
export const getTestimonialsFromConfig = async (): Promise<{
|
|
604
|
+
title: string
|
|
605
|
+
subtitle: string | null
|
|
606
|
+
testimonials: TestimonialItem[]
|
|
607
|
+
}> => {
|
|
608
|
+
const config = await getDynamicConfig()
|
|
609
|
+
return getTestimonialsFromPageInput(config?.["homepage-config"] ?? null)
|
|
610
|
+
}
|
|
637
611
|
|
|
638
|
-
|
|
639
|
-
|
|
612
|
+
export function getSocialLinksFromPageInput(
|
|
613
|
+
pageInput?: HomepageConfig | null
|
|
614
|
+
): Array<{ name: string; url: string; icon?: string }> | null {
|
|
615
|
+
const config = resolvePageInput(pageInput)
|
|
616
|
+
const socialLinksRaw = config["social-links"] || []
|
|
617
|
+
const socialLinks = socialLinksRaw
|
|
618
|
+
.map((item: Record<string, unknown>) => {
|
|
619
|
+
const link = (item?.["social-link"] || item) as Record<string, unknown>
|
|
640
620
|
return {
|
|
641
|
-
name: link?.["social-platform-name"] || "",
|
|
642
|
-
url: link?.["social-platform-url"] || "",
|
|
643
|
-
icon: link?.["social-platform-icon"]
|
|
621
|
+
name: String(link?.["social-platform-name"] || ""),
|
|
622
|
+
url: String(link?.["social-platform-url"] || ""),
|
|
623
|
+
icon: link?.["social-platform-icon"] as string | undefined,
|
|
644
624
|
}
|
|
645
|
-
})
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return socialLinks.length > 0 ? socialLinks : null
|
|
649
|
-
} catch (error) {
|
|
650
|
-
|
|
651
|
-
return null
|
|
652
|
-
}
|
|
625
|
+
})
|
|
626
|
+
.filter((l) => l.name && l.url)
|
|
627
|
+
return socialLinks.length > 0 ? socialLinks : null
|
|
653
628
|
}
|
|
654
629
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
630
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
631
|
+
export const getSocialLinksFromConfig = async (): Promise<Array<{
|
|
632
|
+
name: string
|
|
633
|
+
url: string
|
|
634
|
+
icon?: string
|
|
635
|
+
}> | null> => {
|
|
636
|
+
const config = await getDynamicConfig()
|
|
637
|
+
return getSocialLinksFromPageInput(config?.["homepage-config"] ?? null)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function getFaqsFromPageInput(
|
|
641
|
+
pageInput?: HomepageConfig | null
|
|
642
|
+
): Array<{ category: string; items: Array<{ q: string; a: string }> }> | null {
|
|
643
|
+
const config = resolvePageInput(pageInput)
|
|
644
|
+
const faqsRaw = config["faq-array"] || []
|
|
645
|
+
if (!Array.isArray(faqsRaw) || faqsRaw.length === 0) return null
|
|
646
|
+
const categoriesMap: Record<
|
|
647
|
+
string,
|
|
648
|
+
{ displayCategory: string; items: Array<{ q: string; a: string }> }
|
|
649
|
+
> = {}
|
|
650
|
+
faqsRaw.forEach((item: Record<string, unknown>) => {
|
|
651
|
+
const faq = (item?.["faq"] || item) as Record<string, unknown>
|
|
652
|
+
const displayCategory = String(faq?.["faq-category"] || "General Questions").trim()
|
|
653
|
+
const categoryKey = displayCategory.toLowerCase()
|
|
654
|
+
const q = String(faq?.["faq-question"] || "").trim()
|
|
655
|
+
const a = String(faq?.["faq-answer"] || "").trim()
|
|
656
|
+
if (q && a) {
|
|
657
|
+
if (!categoriesMap[categoryKey]) {
|
|
658
|
+
categoriesMap[categoryKey] = { displayCategory, items: [] }
|
|
674
659
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
660
|
+
categoriesMap[categoryKey].items.push({ q, a })
|
|
661
|
+
}
|
|
662
|
+
})
|
|
663
|
+
const faqs = Object.values(categoriesMap).map((catData) => ({
|
|
664
|
+
category: catData.displayCategory,
|
|
665
|
+
items: catData.items,
|
|
666
|
+
}))
|
|
667
|
+
return faqs.length > 0 ? faqs : null
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
671
|
+
export const getFaqsFromConfig = async (): Promise<Array<{
|
|
672
|
+
category: string
|
|
673
|
+
items: Array<{ q: string; a: string }>
|
|
674
|
+
}> | null> => {
|
|
675
|
+
const config = await getDynamicConfig()
|
|
676
|
+
return getFaqsFromPageInput(config?.["homepage-config"] ?? null)
|
|
677
|
+
}
|
|
681
678
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
679
|
+
export function getAnnouncementMessagesFromPageInput(
|
|
680
|
+
pageInput?: HomepageConfig | null
|
|
681
|
+
): string[] {
|
|
682
|
+
const block = getMergedSectionBlock("promoAnnouncements", pageInput)
|
|
683
|
+
return parseAnnouncementMessages(block)
|
|
686
684
|
}
|
|
685
|
+
|
|
686
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
687
687
|
export const getAnnouncementMessagesFromConfig = async (): Promise<string[]> => {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return parseAnnouncementMessages(block)
|
|
691
|
-
} catch {
|
|
692
|
-
return parseAnnouncementMessages(
|
|
693
|
-
DEFAULT_HOMEPAGE_SECTIONS.promoAnnouncements as Record<string, unknown>
|
|
694
|
-
)
|
|
695
|
-
}
|
|
688
|
+
const config = await getDynamicConfig()
|
|
689
|
+
return getAnnouncementMessagesFromPageInput(config?.["homepage-config"] ?? null)
|
|
696
690
|
}
|
|
697
691
|
|
|
698
692
|
export type AboutBrandConfig = {
|
|
@@ -703,91 +697,81 @@ export type AboutBrandConfig = {
|
|
|
703
697
|
stats: Array<{ label: string; value: string }>
|
|
704
698
|
}
|
|
705
699
|
|
|
706
|
-
export
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
readMoreHref: String(b.readMoreHref ?? b["read-more-link"] ?? "/about"),
|
|
739
|
-
stats,
|
|
740
|
-
}
|
|
741
|
-
} catch {
|
|
742
|
-
const b = DEFAULT_HOMEPAGE_SECTIONS.aboutBrand
|
|
743
|
-
const copy = parseSectionCopy(b)
|
|
744
|
-
return {
|
|
745
|
-
eyebrow: copy.eyebrow ?? "",
|
|
746
|
-
title: copy.title ?? copy.name ?? "",
|
|
747
|
-
description: copy.description ?? copy.text ?? "",
|
|
748
|
-
readMoreHref: String(b.readMoreHref ?? "/about"),
|
|
749
|
-
stats: (b.stats ?? []) as unknown as Array<{ label: string; value: string }>,
|
|
750
|
-
}
|
|
700
|
+
export function getAboutBrandFromPageInput(
|
|
701
|
+
pageInput?: HomepageConfig | null
|
|
702
|
+
): AboutBrandConfig {
|
|
703
|
+
const homepageConfig = resolvePageInput(pageInput)
|
|
704
|
+
const merged = getMergedSectionBlock("aboutBrand", pageInput)
|
|
705
|
+
const legacy =
|
|
706
|
+
homepageConfig["about-brand"] ?? homepageConfig["who-we-are"]
|
|
707
|
+
const b = mergeSectionBlock(
|
|
708
|
+
merged,
|
|
709
|
+
legacy && typeof legacy === "object"
|
|
710
|
+
? (legacy as Record<string, unknown>)
|
|
711
|
+
: null
|
|
712
|
+
)
|
|
713
|
+
const statsRaw = (b.stats ?? b["about-stats"] ?? []) as unknown[]
|
|
714
|
+
const stats = Array.isArray(statsRaw)
|
|
715
|
+
? statsRaw
|
|
716
|
+
.map((s: unknown) => {
|
|
717
|
+
if (!s || typeof s !== "object") return null
|
|
718
|
+
const row = s as Record<string, unknown>
|
|
719
|
+
const label = String(row.label ?? row.name ?? "").trim()
|
|
720
|
+
const value = String(row.value ?? "").trim()
|
|
721
|
+
return label && value ? { label, value } : null
|
|
722
|
+
})
|
|
723
|
+
.filter(Boolean) as Array<{ label: string; value: string }>
|
|
724
|
+
: []
|
|
725
|
+
const copy = parseSectionCopy(b)
|
|
726
|
+
return {
|
|
727
|
+
eyebrow: copy.eyebrow ?? "",
|
|
728
|
+
title: copy.title ?? copy.name ?? "",
|
|
729
|
+
description: copy.description ?? copy.text ?? "",
|
|
730
|
+
readMoreHref: String(b.readMoreHref ?? b["read-more-link"] ?? "/about"),
|
|
731
|
+
stats,
|
|
751
732
|
}
|
|
752
733
|
}
|
|
753
734
|
|
|
735
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
736
|
+
export const getAboutBrandFromConfig = async (): Promise<AboutBrandConfig> => {
|
|
737
|
+
const config = await getDynamicConfig()
|
|
738
|
+
return getAboutBrandFromPageInput(config?.["homepage-config"] ?? null)
|
|
739
|
+
}
|
|
740
|
+
|
|
754
741
|
export type BrandPillarConfig = {
|
|
755
742
|
title: string
|
|
756
743
|
description: string
|
|
757
744
|
image?: string
|
|
758
745
|
}
|
|
759
746
|
|
|
747
|
+
export function getBrandPillarsFromPageInput(
|
|
748
|
+
pageInput?: HomepageConfig | null
|
|
749
|
+
): { sectionTitle: string; pillars: BrandPillarConfig[] } {
|
|
750
|
+
const block = getMergedSectionBlock("brandPillars", pageInput)
|
|
751
|
+
const copy = parseSectionCopy(block)
|
|
752
|
+
const sectionTitle = copy.title ?? copy.name ?? ""
|
|
753
|
+
const raw = block.pillars ?? block["brand-pillars"] ?? []
|
|
754
|
+
const pillars = (Array.isArray(raw) ? raw : [])
|
|
755
|
+
.map((item: unknown) => {
|
|
756
|
+
if (!item || typeof item !== "object") return null
|
|
757
|
+
const row = (item as Record<string, unknown>)["pillar"] ?? item
|
|
758
|
+
const p = row as Record<string, unknown>
|
|
759
|
+
const title = String(p.title ?? p.name ?? "").trim()
|
|
760
|
+
const description = String(p.description ?? p.text ?? "").trim()
|
|
761
|
+
const image = (p.image ?? p.icon) as string | undefined
|
|
762
|
+
return title && description ? { title, description, image } : null
|
|
763
|
+
})
|
|
764
|
+
.filter(Boolean) as BrandPillarConfig[]
|
|
765
|
+
return { sectionTitle, pillars }
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
760
769
|
export const getBrandPillarsFromConfig = async (): Promise<{
|
|
761
770
|
sectionTitle: string
|
|
762
771
|
pillars: BrandPillarConfig[]
|
|
763
772
|
}> => {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const copy = parseSectionCopy(block)
|
|
767
|
-
const sectionTitle = copy.title ?? copy.name ?? ""
|
|
768
|
-
const raw = block.pillars ?? block["brand-pillars"] ?? []
|
|
769
|
-
const pillars = (Array.isArray(raw) ? raw : [])
|
|
770
|
-
.map((item: unknown) => {
|
|
771
|
-
if (!item || typeof item !== "object") return null
|
|
772
|
-
const row = (item as Record<string, unknown>)["pillar"] ?? item
|
|
773
|
-
const p = row as Record<string, unknown>
|
|
774
|
-
const title = String(p.title ?? p.name ?? "").trim()
|
|
775
|
-
const description = String(p.description ?? p.text ?? "").trim()
|
|
776
|
-
const image = (p.image ?? p.icon) as string | undefined
|
|
777
|
-
return title && description ? { title, description, image } : null
|
|
778
|
-
})
|
|
779
|
-
.filter(Boolean) as BrandPillarConfig[]
|
|
780
|
-
|
|
781
|
-
return { sectionTitle, pillars }
|
|
782
|
-
} catch {
|
|
783
|
-
const block = DEFAULT_HOMEPAGE_SECTIONS.brandPillars
|
|
784
|
-
const copy = parseSectionCopy(block)
|
|
785
|
-
const raw = block.pillars ?? []
|
|
786
|
-
return {
|
|
787
|
-
sectionTitle: copy.title ?? copy.name ?? "",
|
|
788
|
-
pillars: [...(Array.isArray(raw) ? raw : [])] as BrandPillarConfig[],
|
|
789
|
-
}
|
|
790
|
-
}
|
|
773
|
+
const config = await getDynamicConfig()
|
|
774
|
+
return getBrandPillarsFromPageInput(config?.["homepage-config"] ?? null)
|
|
791
775
|
}
|
|
792
776
|
|
|
793
777
|
export type BlogPostConfig = {
|
|
@@ -799,43 +783,35 @@ export type BlogPostConfig = {
|
|
|
799
783
|
image?: string
|
|
800
784
|
}
|
|
801
785
|
|
|
786
|
+
export function getBlogPostsFromPageInput(
|
|
787
|
+
pageInput?: HomepageConfig | null
|
|
788
|
+
): BlogPostConfig[] {
|
|
789
|
+
const block = getMergedSectionBlock("blogPosts", pageInput)
|
|
790
|
+
const raw = block.posts ?? block["blog-posts"] ?? block["blog-array"] ?? []
|
|
791
|
+
return (Array.isArray(raw) ? raw : [])
|
|
792
|
+
.map((item: unknown) => {
|
|
793
|
+
if (!item || typeof item !== "object") return null
|
|
794
|
+
const row = (item as Record<string, unknown>)["blog"] ?? item
|
|
795
|
+
const b = row as Record<string, unknown>
|
|
796
|
+
const title = String(b.title ?? "").trim()
|
|
797
|
+
const href = String(b.href ?? b.link ?? b.url ?? "").trim()
|
|
798
|
+
if (!title || !href) return null
|
|
799
|
+
return {
|
|
800
|
+
title,
|
|
801
|
+
href,
|
|
802
|
+
excerpt: String(b.excerpt ?? b.description ?? "").trim() || undefined,
|
|
803
|
+
category: String(b.category ?? b.tag ?? "").trim() || undefined,
|
|
804
|
+
date: String(b.date ?? "").trim() || undefined,
|
|
805
|
+
image: (b.image ?? b.thumbnail) as string | undefined,
|
|
806
|
+
}
|
|
807
|
+
})
|
|
808
|
+
.filter(Boolean) as BlogPostConfig[]
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
802
812
|
export const getBlogPostsFromConfig = async (): Promise<BlogPostConfig[]> => {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const raw = block.posts ?? block["blog-posts"] ?? block["blog-array"] ?? []
|
|
806
|
-
const posts = (Array.isArray(raw) ? raw : [])
|
|
807
|
-
.map((item: unknown) => {
|
|
808
|
-
if (!item || typeof item !== "object") return null
|
|
809
|
-
const row = (item as Record<string, unknown>)["blog"] ?? item
|
|
810
|
-
const b = row as Record<string, unknown>
|
|
811
|
-
const title = String(b.title ?? "").trim()
|
|
812
|
-
const href = String(b.href ?? b.link ?? b.url ?? "").trim()
|
|
813
|
-
if (!title || !href) return null
|
|
814
|
-
return {
|
|
815
|
-
title,
|
|
816
|
-
href,
|
|
817
|
-
excerpt: String(b.excerpt ?? b.description ?? "").trim() || undefined,
|
|
818
|
-
category: String(b.category ?? b.tag ?? "").trim() || undefined,
|
|
819
|
-
date: String(b.date ?? "").trim() || undefined,
|
|
820
|
-
image: (b.image ?? b.thumbnail) as string | undefined,
|
|
821
|
-
}
|
|
822
|
-
})
|
|
823
|
-
.filter(Boolean) as BlogPostConfig[]
|
|
824
|
-
return posts
|
|
825
|
-
} catch {
|
|
826
|
-
const block = DEFAULT_HOMEPAGE_SECTIONS.blogPosts
|
|
827
|
-
const raw = block.posts ?? []
|
|
828
|
-
return (Array.isArray(raw) ? raw : [])
|
|
829
|
-
.map((item: unknown) => {
|
|
830
|
-
if (!item || typeof item !== "object") return null
|
|
831
|
-
const b = item as Record<string, unknown>
|
|
832
|
-
const title = String(b.title ?? "").trim()
|
|
833
|
-
const href = String(b.href ?? b.link ?? "").trim()
|
|
834
|
-
if (!title || !href) return null
|
|
835
|
-
return { title, href } as BlogPostConfig
|
|
836
|
-
})
|
|
837
|
-
.filter(Boolean) as BlogPostConfig[]
|
|
838
|
-
}
|
|
813
|
+
const config = await getDynamicConfig()
|
|
814
|
+
return getBlogPostsFromPageInput(config?.["homepage-config"] ?? null)
|
|
839
815
|
}
|
|
840
816
|
|
|
841
817
|
function readPromoCountdownBlock(block: unknown): {
|
|
@@ -843,9 +819,18 @@ function readPromoCountdownBlock(block: unknown): {
|
|
|
843
819
|
message: string | null
|
|
844
820
|
endAt: string | null
|
|
845
821
|
active: boolean | undefined
|
|
822
|
+
ctaLabel: string | null
|
|
823
|
+
ctaHref: string | null
|
|
846
824
|
} {
|
|
847
825
|
if (!block || typeof block !== "object") {
|
|
848
|
-
return {
|
|
826
|
+
return {
|
|
827
|
+
code: null,
|
|
828
|
+
message: null,
|
|
829
|
+
endAt: null,
|
|
830
|
+
active: undefined,
|
|
831
|
+
ctaLabel: null,
|
|
832
|
+
ctaHref: null,
|
|
833
|
+
}
|
|
849
834
|
}
|
|
850
835
|
const row = block as Record<string, unknown>
|
|
851
836
|
const code =
|
|
@@ -856,52 +841,179 @@ function readPromoCountdownBlock(block: unknown): {
|
|
|
856
841
|
const endAt = typeof endAtRaw === "string" && endAtRaw.trim() ? endAtRaw.trim() : null
|
|
857
842
|
const activeRaw = row["promo-active"] ?? row.active
|
|
858
843
|
const active = typeof activeRaw === "boolean" ? activeRaw : undefined
|
|
859
|
-
|
|
844
|
+
const ctaLabel =
|
|
845
|
+
normalizeCmsText(row["cta-label"] ?? row.ctaLabel ?? row["button-label"]) ?? null
|
|
846
|
+
const ctaHref =
|
|
847
|
+
normalizeCmsText(row["cta-href"] ?? row.ctaHref ?? row["button-href"]) ?? null
|
|
848
|
+
return { code, message, endAt, active, ctaLabel, ctaHref }
|
|
860
849
|
}
|
|
861
850
|
|
|
862
851
|
/** Promo countdown — defaults + `sections.promoCountdown` override. */
|
|
852
|
+
export function getPromoCountdownFromPageInput(
|
|
853
|
+
pageInput?: HomepageConfig | null
|
|
854
|
+
): {
|
|
855
|
+
code: string | null
|
|
856
|
+
message: string | null
|
|
857
|
+
endAt: string | null
|
|
858
|
+
active: boolean
|
|
859
|
+
ctaLabel: string | null
|
|
860
|
+
ctaHref: string | null
|
|
861
|
+
} {
|
|
862
|
+
const block = getMergedSectionBlock("promoCountdown", pageInput)
|
|
863
|
+
const fromBlock = readPromoCountdownBlock(block)
|
|
864
|
+
const defaults = readPromoCountdownBlock(DEFAULT_HOMEPAGE_SECTIONS.promoCountdown)
|
|
865
|
+
return {
|
|
866
|
+
code: fromBlock.code ?? defaults.code,
|
|
867
|
+
message: fromBlock.message ?? defaults.message,
|
|
868
|
+
endAt: fromBlock.endAt ?? defaults.endAt,
|
|
869
|
+
active: fromBlock.active ?? defaults.active ?? false,
|
|
870
|
+
ctaLabel: fromBlock.ctaLabel ?? defaults.ctaLabel,
|
|
871
|
+
ctaHref: fromBlock.ctaHref ?? defaults.ctaHref,
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
863
876
|
export const getPromoCountdownFromConfig = async (): Promise<{
|
|
864
877
|
code: string | null
|
|
865
878
|
message: string | null
|
|
866
879
|
endAt: string | null
|
|
867
880
|
active: boolean
|
|
881
|
+
ctaLabel: string | null
|
|
882
|
+
ctaHref: string | null
|
|
868
883
|
}> => {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const defaults = readPromoCountdownBlock(DEFAULT_HOMEPAGE_SECTIONS.promoCountdown)
|
|
884
|
+
const config = await getDynamicConfig()
|
|
885
|
+
return getPromoCountdownFromPageInput(config?.["homepage-config"] ?? null)
|
|
886
|
+
}
|
|
873
887
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
+
export type InstagramPostConfig = {
|
|
889
|
+
id: string
|
|
890
|
+
image: string
|
|
891
|
+
url?: string
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
export type InstagramPostsData = {
|
|
895
|
+
title: string
|
|
896
|
+
handle: string
|
|
897
|
+
profileUrl: string
|
|
898
|
+
profileImage?: string
|
|
899
|
+
posts: InstagramPostConfig[]
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function parseInstagramPostRow(
|
|
903
|
+
item: unknown,
|
|
904
|
+
index: number
|
|
905
|
+
): InstagramPostConfig | null {
|
|
906
|
+
if (!item || typeof item !== "object") return null
|
|
907
|
+
const row = (item as Record<string, unknown>).post ??
|
|
908
|
+
(item as Record<string, unknown>).instagram ??
|
|
909
|
+
item
|
|
910
|
+
const p = row as Record<string, unknown>
|
|
911
|
+
const image = String(p.image ?? p["post-image"] ?? p.thumbnail ?? p.src ?? "").trim()
|
|
912
|
+
if (!image) return null
|
|
913
|
+
const url = String(p.url ?? p.link ?? p.href ?? p["post-url"] ?? "").trim() || undefined
|
|
914
|
+
return {
|
|
915
|
+
id: String(p.id ?? `ig-${index}`),
|
|
916
|
+
image,
|
|
917
|
+
url,
|
|
888
918
|
}
|
|
889
919
|
}
|
|
890
920
|
|
|
891
|
-
|
|
921
|
+
function parseInstagramPostsFromBlock(
|
|
922
|
+
block: Record<string, unknown>
|
|
923
|
+
): InstagramPostConfig[] {
|
|
924
|
+
const raw =
|
|
925
|
+
block.posts ??
|
|
926
|
+
block.items ??
|
|
927
|
+
block["instagram-posts"] ??
|
|
928
|
+
block["posts-array"] ??
|
|
929
|
+
[]
|
|
930
|
+
if (!Array.isArray(raw)) return []
|
|
931
|
+
return raw
|
|
932
|
+
.map((item, index) => parseInstagramPostRow(item, index))
|
|
933
|
+
.filter((p): p is InstagramPostConfig => p !== null)
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/** Instagram feed — `sections.instagramPosts` + optional social-links fallback. */
|
|
937
|
+
export function getInstagramPostsFromPageInput(
|
|
938
|
+
pageInput?: HomepageConfig | null
|
|
939
|
+
): InstagramPostsData {
|
|
940
|
+
const defaultsBlock = DEFAULT_HOMEPAGE_SECTIONS.instagramPosts as Record<
|
|
941
|
+
string,
|
|
942
|
+
unknown
|
|
943
|
+
>
|
|
944
|
+
const defaultCopy = parseSectionCopy(defaultsBlock)
|
|
945
|
+
const defaultPosts = parseInstagramPostsFromBlock(defaultsBlock)
|
|
946
|
+
const block = getMergedSectionBlock("instagramPosts", pageInput)
|
|
947
|
+
const copy = parseSectionCopy(block)
|
|
948
|
+
let posts = parseInstagramPostsFromBlock(block)
|
|
949
|
+
if (posts.length === 0) posts = defaultPosts
|
|
950
|
+
const handle =
|
|
951
|
+
normalizeCmsText(
|
|
952
|
+
block.handle ??
|
|
953
|
+
block.username ??
|
|
954
|
+
block["instagram-handle"] ??
|
|
955
|
+
defaultsBlock.handle
|
|
956
|
+
) ?? "yourbrand"
|
|
957
|
+
const profileUrl =
|
|
958
|
+
normalizeCmsText(
|
|
959
|
+
block["profile-url"] ??
|
|
960
|
+
block.profileUrl ??
|
|
961
|
+
block["instagram-url"] ??
|
|
962
|
+
defaultsBlock["profile-url"]
|
|
963
|
+
) ?? `https://www.instagram.com/${handle.replace(/^@/, "")}/`
|
|
964
|
+
const profileImage =
|
|
965
|
+
(block["profile-image"] ??
|
|
966
|
+
block.profileImage ??
|
|
967
|
+
defaultsBlock["profile-image"]) as string | undefined
|
|
968
|
+
return {
|
|
969
|
+
title:
|
|
970
|
+
copy.title ?? copy.name ?? defaultCopy.title ?? "Follow Us on Instagram",
|
|
971
|
+
handle,
|
|
972
|
+
profileUrl,
|
|
973
|
+
profileImage,
|
|
974
|
+
posts,
|
|
975
|
+
}
|
|
976
|
+
}
|
|
892
977
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
978
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
979
|
+
export const getInstagramPostsFromConfig = async (): Promise<InstagramPostsData> => {
|
|
980
|
+
const config = await getDynamicConfig()
|
|
981
|
+
return getInstagramPostsFromPageInput(config?.["homepage-config"] ?? null)
|
|
982
|
+
}
|
|
897
983
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
984
|
+
export {
|
|
985
|
+
DEFAULT_HOMEPAGE_SECTIONS,
|
|
986
|
+
type DefaultHomeSectionId,
|
|
987
|
+
} from "./homepage-section-defaults"
|
|
988
|
+
export type {
|
|
989
|
+
DynamicConfigResponse,
|
|
990
|
+
HomepageConfig,
|
|
991
|
+
HomepageSectionsConfig,
|
|
992
|
+
HomepageSectionId,
|
|
993
|
+
HomepageSectionCopyFields,
|
|
994
|
+
HomepageNewArrivalsSection,
|
|
995
|
+
} from "./homepage-config.types"
|
|
996
|
+
export { HOMEPAGE_SECTION_IDS } from "./homepage-config.types"
|
|
997
|
+
|
|
998
|
+
export function getPromoBarFromPageInput(
|
|
999
|
+
pageInput?: HomepageConfig | null
|
|
1000
|
+
): { text: string | null; code: string | null; value: string | null; active: boolean } {
|
|
1001
|
+
const config = resolvePageInput(pageInput)
|
|
1002
|
+
const promoConfig = config["promo-bar"]
|
|
1003
|
+
const text = normalizeCmsText(promoConfig?.["promo-text"]) ?? null
|
|
1004
|
+
const code = normalizeCmsText(promoConfig?.["promo-code"]) ?? null
|
|
1005
|
+
const value = normalizeCmsText(promoConfig?.["promo-value"]) ?? null
|
|
1006
|
+
const active = Boolean(promoConfig?.["promo-active"])
|
|
1007
|
+
return { text, code, value, active }
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
1011
|
+
export const getPromoBarConfig = async (): Promise<{
|
|
1012
|
+
text: string | null
|
|
1013
|
+
code: string | null
|
|
1014
|
+
value: string | null
|
|
1015
|
+
active: boolean
|
|
1016
|
+
}> => {
|
|
1017
|
+
const config = await getDynamicConfig()
|
|
1018
|
+
return getPromoBarFromPageInput(config?.["homepage-config"] ?? null)
|
|
907
1019
|
}
|