medusa-storefront-data 2.1.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/src/server/home.js +122 -0
- 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/src/server/homepage-section-defaults.d.ts +188 -0
- package/dist/src/server/homepage-section-defaults.d.ts.map +1 -0
- package/dist/src/server/homepage-section-defaults.js +188 -0
- 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 +838 -248
- 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 +173 -16
- package/src/server/homepage-config.types.ts +335 -0
- package/src/server/homepage-section-defaults.ts +199 -0
- 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 -125
- package/dist/server/dynamic-config.d.ts.map +0 -1
- package/dist/server/dynamic-config.js +0 -289
- 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 -15
- package/dist/server/home.d.ts.map +0 -1
- package/dist/server/home.js +0 -45
- 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,73 +1,28 @@
|
|
|
1
|
-
"
|
|
1
|
+
import "server-only"
|
|
2
2
|
|
|
3
3
|
import { cache } from "react"
|
|
4
4
|
import { getAuthHeaders } from "../cookies"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"app-banner-button-name"?: string
|
|
27
|
-
"app-banner-button-link"?: string
|
|
28
|
-
"app-banner-link"?: string
|
|
29
|
-
}
|
|
30
|
-
}>
|
|
31
|
-
"website-description"?: string
|
|
32
|
-
"website_description"?: string
|
|
33
|
-
"why-choose-us-title"?: string
|
|
34
|
-
"why-choose-us-features"?: Array<{
|
|
35
|
-
feature?: {
|
|
36
|
-
"feature-name"?: string
|
|
37
|
-
"feature-icon"?: string
|
|
38
|
-
}
|
|
39
|
-
// Handle potential direct structure if any
|
|
40
|
-
"feature-name"?: string
|
|
41
|
-
"feature-icon"?: string
|
|
42
|
-
}>
|
|
43
|
-
"contact-us"?: {
|
|
44
|
-
"contact-phone"?: string
|
|
45
|
-
"contact-email"?: string
|
|
46
|
-
"contact-address"?: string
|
|
47
|
-
}
|
|
48
|
-
"social-links"?: Array<{
|
|
49
|
-
"social-link"?: {
|
|
50
|
-
"social-platform-name"?: string
|
|
51
|
-
"social-platform-icon"?: string
|
|
52
|
-
"social-platform-url"?: string
|
|
53
|
-
}
|
|
54
|
-
// Handle potential direct structure if any
|
|
55
|
-
"social-platform-name"?: string
|
|
56
|
-
"social-platform-icon"?: string
|
|
57
|
-
"social-platform-url"?: string
|
|
58
|
-
}>
|
|
59
|
-
"promo-bar"?: {
|
|
60
|
-
"promo-text"?: string
|
|
61
|
-
"promo-code"?: string
|
|
62
|
-
"promo-value"?: string
|
|
63
|
-
"promo-active"?: boolean
|
|
64
|
-
}
|
|
65
|
-
// Allow for other properties
|
|
66
|
-
[key: string]: any
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const getDynamicConfig = cache(async (): Promise<DynamicConfig | null> => {
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_HOMEPAGE_SECTIONS,
|
|
7
|
+
HOME_SECTION_COPY_ID_LIST,
|
|
8
|
+
type DefaultHomeSectionId,
|
|
9
|
+
} from "./homepage-section-defaults"
|
|
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> => {
|
|
71
26
|
try {
|
|
72
27
|
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
73
28
|
const baseUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
@@ -116,16 +71,17 @@ export const getDynamicConfig = cache(async (): Promise<DynamicConfig | null> =>
|
|
|
116
71
|
}
|
|
117
72
|
})
|
|
118
73
|
|
|
119
|
-
export
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
} catch (error) {
|
|
74
|
+
export function getLogoFromPageInput(
|
|
75
|
+
pageInput?: HomepageConfig | null
|
|
76
|
+
): string | null {
|
|
77
|
+
const config = resolvePageInput(pageInput)
|
|
78
|
+
return config.logo || null
|
|
79
|
+
}
|
|
126
80
|
|
|
127
|
-
|
|
128
|
-
|
|
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)
|
|
129
85
|
}
|
|
130
86
|
|
|
131
87
|
export const getBannerImageFromConfig = async (): Promise<string | null> => {
|
|
@@ -169,68 +125,67 @@ export const getMobileBannerFromConfig = async (): Promise<{ image: string | nul
|
|
|
169
125
|
}
|
|
170
126
|
}
|
|
171
127
|
|
|
172
|
-
export
|
|
173
|
-
image?: string
|
|
174
|
-
title?: string
|
|
175
|
-
subtitle?: string
|
|
176
|
-
description?: string
|
|
177
|
-
buttonName?: string
|
|
178
|
-
buttonLink?: string
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
const config = await getDynamicConfig()
|
|
182
|
-
const bannerArray = config?.["homepage-config"]?.["homepage-banner-array"]
|
|
183
|
-
|
|
184
|
-
if (bannerArray && bannerArray.length > 0) {
|
|
185
|
-
return bannerArray.map(item => {
|
|
186
|
-
const banner = item["homepage-banner"]
|
|
187
|
-
return {
|
|
188
|
-
image: banner?.["homepage-banner-image"],
|
|
189
|
-
title: normalizeCmsText(banner?.["homepage-banner-title"]),
|
|
190
|
-
subtitle: normalizeCmsText(banner?.["homepage-banner-subtitle"]),
|
|
191
|
-
description: normalizeCmsText(banner?.["homepage-banner-description"]),
|
|
192
|
-
buttonName: normalizeBannerButtonName(banner?.["homepage-banner-button-name"]),
|
|
193
|
-
buttonLink: banner?.["homepage-banner-button-link"],
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
return null
|
|
198
|
-
} catch (error) {
|
|
128
|
+
export type HomepageBanner = {
|
|
129
|
+
image?: string
|
|
130
|
+
title?: string
|
|
131
|
+
subtitle?: string
|
|
132
|
+
description?: string
|
|
133
|
+
buttonName?: string
|
|
134
|
+
buttonLink?: string
|
|
135
|
+
}
|
|
199
136
|
|
|
200
|
-
|
|
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
|
+
})
|
|
201
154
|
}
|
|
155
|
+
return null
|
|
202
156
|
}
|
|
203
157
|
|
|
204
|
-
export
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
178
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
title: normalizeCmsText(banner?.["app-banner-title"]),
|
|
222
|
-
subtitle: normalizeCmsText(banner?.["app-banner-subtitle"]),
|
|
223
|
-
description: normalizeCmsText(banner?.["app-banner-description"]),
|
|
224
|
-
buttonName: normalizeBannerButtonName(banner?.["app-banner-button-name"]),
|
|
225
|
-
buttonLink: banner?.["app-banner-button-link"],
|
|
226
|
-
}
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
return null
|
|
230
|
-
} 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
|
+
}
|
|
231
184
|
|
|
232
|
-
|
|
233
|
-
|
|
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)
|
|
234
189
|
}
|
|
235
190
|
|
|
236
191
|
// Legacy function for backward compatibility
|
|
@@ -239,44 +194,56 @@ export const getMobileBannerImageFromConfig = async (): Promise<string | null> =
|
|
|
239
194
|
return image
|
|
240
195
|
}
|
|
241
196
|
|
|
242
|
-
export
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return desc || null
|
|
250
|
-
} catch (error) {
|
|
251
|
-
|
|
252
|
-
return null
|
|
253
|
-
}
|
|
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
|
|
254
203
|
}
|
|
255
204
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (!homepageConfig) return null
|
|
262
|
-
|
|
263
|
-
const title = homepageConfig["why-choose-us-title"] || "Why Choose Us?"
|
|
264
|
-
const featuresRaw = homepageConfig["why-choose-us-features"] || []
|
|
265
|
-
|
|
266
|
-
const features = featuresRaw.map((item: any) => {
|
|
267
|
-
const feature = item?.feature || item
|
|
268
|
-
return {
|
|
269
|
-
name: feature?.["feature-name"] || "",
|
|
270
|
-
icon: feature?.["feature-icon"] || "",
|
|
271
|
-
}
|
|
272
|
-
}).filter((f: any) => f.name && f.icon)
|
|
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)
|
|
209
|
+
}
|
|
273
210
|
|
|
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
|
+
}
|
|
274
225
|
|
|
275
|
-
|
|
276
|
-
|
|
226
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
227
|
+
export const getFeaturesFromConfig = async (): Promise<{
|
|
228
|
+
title: string
|
|
229
|
+
features: Array<{ name: string; icon: string }>
|
|
230
|
+
}> => {
|
|
231
|
+
const config = await getDynamicConfig()
|
|
232
|
+
return getFeaturesFromPageInput(config?.["homepage-config"] ?? null)
|
|
233
|
+
}
|
|
277
234
|
|
|
278
|
-
|
|
279
|
-
|
|
235
|
+
function parseFeatureIconList(raw: unknown): Array<{ name: string; icon: string }> {
|
|
236
|
+
if (!Array.isArray(raw)) return []
|
|
237
|
+
return raw
|
|
238
|
+
.map((item: unknown) => {
|
|
239
|
+
if (!item || typeof item !== "object") return null
|
|
240
|
+
const feature = (item as Record<string, unknown>).feature ?? item
|
|
241
|
+
const row = feature as Record<string, unknown>
|
|
242
|
+
const name = normalizeCmsText(row["feature-name"] ?? row.name) ?? ""
|
|
243
|
+
const icon = normalizeCmsText(row["feature-icon"] ?? row.icon) ?? ""
|
|
244
|
+
return name && icon ? { name, icon } : null
|
|
245
|
+
})
|
|
246
|
+
.filter(Boolean) as Array<{ name: string; icon: string }>
|
|
280
247
|
}
|
|
281
248
|
|
|
282
249
|
const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
@@ -292,6 +259,176 @@ function normalizeCmsText(value: unknown): string | undefined {
|
|
|
292
259
|
.replace(/≡ƒÿÄ≡ƒæòΓ£¿/g, " 👕✨")
|
|
293
260
|
}
|
|
294
261
|
|
|
262
|
+
/** CMS copy for a homepage section (title, name, text, description, etc.). */
|
|
263
|
+
export type SectionCopy = {
|
|
264
|
+
title: string | null
|
|
265
|
+
name: string | null
|
|
266
|
+
text: string | null
|
|
267
|
+
description: string | null
|
|
268
|
+
subtitle: string | null
|
|
269
|
+
eyebrow: string | null
|
|
270
|
+
/** Layout variant, e.g. new arrivals `grid` | `classic`. */
|
|
271
|
+
variant: string | null
|
|
272
|
+
layout: string | null
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const EMPTY_SECTION_COPY: SectionCopy = {
|
|
276
|
+
title: null,
|
|
277
|
+
name: null,
|
|
278
|
+
text: null,
|
|
279
|
+
description: null,
|
|
280
|
+
subtitle: null,
|
|
281
|
+
eyebrow: null,
|
|
282
|
+
variant: null,
|
|
283
|
+
layout: null,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function parseSectionCopy(block: unknown): SectionCopy {
|
|
287
|
+
if (!block || typeof block !== "object") return { ...EMPTY_SECTION_COPY }
|
|
288
|
+
const row = block as Record<string, unknown>
|
|
289
|
+
return {
|
|
290
|
+
title: normalizeCmsText(row.title) ?? null,
|
|
291
|
+
name: normalizeCmsText(row.name) ?? null,
|
|
292
|
+
text: normalizeCmsText(row.text) ?? null,
|
|
293
|
+
description: normalizeCmsText(row.description) ?? null,
|
|
294
|
+
subtitle: normalizeCmsText(row.subtitle) ?? null,
|
|
295
|
+
eyebrow: normalizeCmsText(row.eyebrow) ?? null,
|
|
296
|
+
variant: normalizeCmsText(row.variant) ?? null,
|
|
297
|
+
layout: normalizeCmsText(row.layout) ?? null,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type HomeSectionCopyId = DefaultHomeSectionId
|
|
302
|
+
|
|
303
|
+
export type HomeSectionsCopy = Record<HomeSectionCopyId, SectionCopy>
|
|
304
|
+
|
|
305
|
+
const HOME_SECTION_COPY_IDS = new Set<HomeSectionCopyId>(HOME_SECTION_COPY_ID_LIST)
|
|
306
|
+
|
|
307
|
+
function sectionIdFromConfigKey(key: string): HomeSectionCopyId | null {
|
|
308
|
+
const id = key.replace(/-([a-z])/g, (_, c: string) =>
|
|
309
|
+
c.toUpperCase()
|
|
310
|
+
) as HomeSectionCopyId
|
|
311
|
+
return HOME_SECTION_COPY_IDS.has(id) ? id : null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getHomepageSectionsRoot(
|
|
315
|
+
pageInput?: HomepageConfig | null
|
|
316
|
+
): Record<string, unknown> {
|
|
317
|
+
const config = resolvePageInput(pageInput)
|
|
318
|
+
const root = config.sections
|
|
319
|
+
return root && typeof root === "object"
|
|
320
|
+
? (root as Record<string, unknown>)
|
|
321
|
+
: {}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function findSectionBlock(
|
|
325
|
+
root: Record<string, unknown>,
|
|
326
|
+
id: HomeSectionCopyId
|
|
327
|
+
): Record<string, unknown> | null {
|
|
328
|
+
for (const [key, value] of Object.entries(root)) {
|
|
329
|
+
if (sectionIdFromConfigKey(key) !== id) continue
|
|
330
|
+
if (value && typeof value === "object") {
|
|
331
|
+
return value as Record<string, unknown>
|
|
332
|
+
}
|
|
333
|
+
if (typeof value === "string") {
|
|
334
|
+
return { text: value }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export { mergeSectionBlock } from "./config-merge"
|
|
341
|
+
|
|
342
|
+
export function getMergedSectionBlock(
|
|
343
|
+
id: HomeSectionCopyId,
|
|
344
|
+
pageInput?: HomepageConfig | null
|
|
345
|
+
): Record<string, unknown> {
|
|
346
|
+
const root = getHomepageSectionsRoot(pageInput)
|
|
347
|
+
const defaults = DEFAULT_HOMEPAGE_SECTIONS[id] ?? {}
|
|
348
|
+
const override = findSectionBlock(root, id)
|
|
349
|
+
return mergeSectionBlock(
|
|
350
|
+
{ ...defaults } as Record<string, unknown>,
|
|
351
|
+
override
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function mergeSectionCopy(base: SectionCopy, patch: SectionCopy): SectionCopy {
|
|
356
|
+
const pick = (override: string | null, fallback: string | null) =>
|
|
357
|
+
override ?? fallback
|
|
358
|
+
return {
|
|
359
|
+
title: pick(patch.title ?? patch.name, base.title ?? base.name),
|
|
360
|
+
name: pick(patch.name, base.name),
|
|
361
|
+
text: pick(patch.text, base.text),
|
|
362
|
+
description: pick(patch.description, base.description),
|
|
363
|
+
subtitle: pick(patch.subtitle, base.subtitle),
|
|
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),
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function parseAnnouncementMessages(block: Record<string, unknown> | null): string[] {
|
|
371
|
+
if (!block) return []
|
|
372
|
+
const raw = block.messages ?? block.items ?? block["announcement-messages"]
|
|
373
|
+
if (!Array.isArray(raw)) return []
|
|
374
|
+
return raw
|
|
375
|
+
.map((item: unknown) => {
|
|
376
|
+
if (typeof item === "string") return item.trim()
|
|
377
|
+
if (item && typeof item === "object") {
|
|
378
|
+
const row = item as Record<string, unknown>
|
|
379
|
+
const nested = (row.message ?? row.text) as unknown
|
|
380
|
+
return typeof nested === "string" ? nested.trim() : ""
|
|
381
|
+
}
|
|
382
|
+
return ""
|
|
383
|
+
})
|
|
384
|
+
.filter(Boolean)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Merged section copy: package defaults + `homepage-config.sections` overrides.
|
|
389
|
+
*/
|
|
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)
|
|
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)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export type TrustFeaturesConfig = {
|
|
408
|
+
title: string | null
|
|
409
|
+
description: string | null
|
|
410
|
+
features: Array<{ name: string; icon: string }>
|
|
411
|
+
}
|
|
412
|
+
|
|
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. */
|
|
427
|
+
export const getTrustFeaturesFromConfig = async (): Promise<TrustFeaturesConfig> => {
|
|
428
|
+
const config = await getDynamicConfig()
|
|
429
|
+
return getTrustFeaturesFromPageInput(config?.["homepage-config"] ?? null)
|
|
430
|
+
}
|
|
431
|
+
|
|
295
432
|
function normalizeBannerButtonName(value: unknown): string | undefined {
|
|
296
433
|
const text = normalizeCmsText(value)
|
|
297
434
|
if (!text) return undefined
|
|
@@ -305,125 +442,578 @@ function normalizeConfigEmail(value: unknown): string | undefined {
|
|
|
305
442
|
return EMAIL_PATTERN.test(trimmed) ? trimmed : undefined
|
|
306
443
|
}
|
|
307
444
|
|
|
308
|
-
export
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
445
|
+
export type ContactInfo = { phone?: string; email?: string; address?: string }
|
|
446
|
+
|
|
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"],
|
|
457
|
+
}
|
|
458
|
+
}
|
|
312
459
|
|
|
313
|
-
|
|
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
|
+
}
|
|
314
465
|
|
|
315
|
-
|
|
316
|
-
|
|
466
|
+
function normalizeVideoStoryUrl(entry: unknown): string | null {
|
|
467
|
+
if (typeof entry === "string" && entry.trim()) return entry.trim()
|
|
468
|
+
if (!entry || typeof entry !== "object") return null
|
|
469
|
+
const row = entry as Record<string, unknown>
|
|
470
|
+
const nested =
|
|
471
|
+
(row["video-story"] as Record<string, unknown> | undefined) ??
|
|
472
|
+
(row["video_stories"] as Record<string, unknown> | undefined) ??
|
|
473
|
+
row
|
|
474
|
+
const url =
|
|
475
|
+
nested["video-url"] ??
|
|
476
|
+
nested["video_url"] ??
|
|
477
|
+
nested.url ??
|
|
478
|
+
nested.src ??
|
|
479
|
+
nested.video
|
|
480
|
+
return typeof url === "string" && url.trim() ? url.trim() : null
|
|
481
|
+
}
|
|
317
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Homepage video stories/reviews carousel (`homepage-config.video-stories`).
|
|
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
|
+
}
|
|
318
508
|
|
|
509
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
510
|
+
export const getVideoStoriesFromConfig = async (): Promise<{
|
|
511
|
+
title: string
|
|
512
|
+
videoUrls: string[]
|
|
513
|
+
} | null> => {
|
|
514
|
+
const config = await getDynamicConfig()
|
|
515
|
+
return getVideoStoriesFromPageInput(config?.["homepage-config"] ?? null)
|
|
516
|
+
}
|
|
319
517
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
518
|
+
export type TestimonialItem = {
|
|
519
|
+
id: string
|
|
520
|
+
text: string
|
|
521
|
+
name: string
|
|
522
|
+
rating?: number
|
|
523
|
+
avatar?: string
|
|
524
|
+
}
|
|
326
525
|
|
|
327
|
-
|
|
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,
|
|
328
553
|
}
|
|
329
554
|
}
|
|
330
555
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
}
|
|
335
568
|
|
|
336
|
-
|
|
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
|
+
}
|
|
577
|
+
|
|
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
|
+
)
|
|
598
|
+
}
|
|
599
|
+
return { title, subtitle, testimonials }
|
|
600
|
+
}
|
|
337
601
|
|
|
338
|
-
|
|
339
|
-
|
|
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
|
+
}
|
|
340
611
|
|
|
341
|
-
|
|
342
|
-
|
|
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>
|
|
343
620
|
return {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
rating: parseFloat(review?.["review-rating"] || "5"),
|
|
348
|
-
avatar: review?.["review-profile-image"] || undefined,
|
|
621
|
+
name: String(link?.["social-platform-name"] || ""),
|
|
622
|
+
url: String(link?.["social-platform-url"] || ""),
|
|
623
|
+
icon: link?.["social-platform-icon"] as string | undefined,
|
|
349
624
|
}
|
|
350
|
-
})
|
|
625
|
+
})
|
|
626
|
+
.filter((l) => l.name && l.url)
|
|
627
|
+
return socialLinks.length > 0 ? socialLinks : null
|
|
628
|
+
}
|
|
351
629
|
|
|
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
|
+
}
|
|
352
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: [] }
|
|
659
|
+
}
|
|
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
|
+
}
|
|
353
669
|
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
356
678
|
|
|
357
|
-
|
|
679
|
+
export function getAnnouncementMessagesFromPageInput(
|
|
680
|
+
pageInput?: HomepageConfig | null
|
|
681
|
+
): string[] {
|
|
682
|
+
const block = getMergedSectionBlock("promoAnnouncements", pageInput)
|
|
683
|
+
return parseAnnouncementMessages(block)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
687
|
+
export const getAnnouncementMessagesFromConfig = async (): Promise<string[]> => {
|
|
688
|
+
const config = await getDynamicConfig()
|
|
689
|
+
return getAnnouncementMessagesFromPageInput(config?.["homepage-config"] ?? null)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export type AboutBrandConfig = {
|
|
693
|
+
eyebrow: string
|
|
694
|
+
title: string
|
|
695
|
+
description: string
|
|
696
|
+
readMoreHref: string
|
|
697
|
+
stats: Array<{ label: string; value: string }>
|
|
698
|
+
}
|
|
699
|
+
|
|
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,
|
|
358
732
|
}
|
|
359
733
|
}
|
|
360
734
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
|
|
741
|
+
export type BrandPillarConfig = {
|
|
742
|
+
title: string
|
|
743
|
+
description: string
|
|
744
|
+
image?: string
|
|
745
|
+
}
|
|
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
|
+
}
|
|
365
767
|
|
|
366
|
-
|
|
367
|
-
|
|
768
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
769
|
+
export const getBrandPillarsFromConfig = async (): Promise<{
|
|
770
|
+
sectionTitle: string
|
|
771
|
+
pillars: BrandPillarConfig[]
|
|
772
|
+
}> => {
|
|
773
|
+
const config = await getDynamicConfig()
|
|
774
|
+
return getBrandPillarsFromPageInput(config?.["homepage-config"] ?? null)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export type BlogPostConfig = {
|
|
778
|
+
title: string
|
|
779
|
+
excerpt?: string
|
|
780
|
+
category?: string
|
|
781
|
+
date?: string
|
|
782
|
+
href: string
|
|
783
|
+
image?: string
|
|
784
|
+
}
|
|
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
|
|
368
799
|
return {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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,
|
|
372
806
|
}
|
|
373
|
-
})
|
|
374
|
-
|
|
807
|
+
})
|
|
808
|
+
.filter(Boolean) as BlogPostConfig[]
|
|
809
|
+
}
|
|
375
810
|
|
|
376
|
-
|
|
377
|
-
|
|
811
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
812
|
+
export const getBlogPostsFromConfig = async (): Promise<BlogPostConfig[]> => {
|
|
813
|
+
const config = await getDynamicConfig()
|
|
814
|
+
return getBlogPostsFromPageInput(config?.["homepage-config"] ?? null)
|
|
815
|
+
}
|
|
378
816
|
|
|
379
|
-
|
|
817
|
+
function readPromoCountdownBlock(block: unknown): {
|
|
818
|
+
code: string | null
|
|
819
|
+
message: string | null
|
|
820
|
+
endAt: string | null
|
|
821
|
+
active: boolean | undefined
|
|
822
|
+
ctaLabel: string | null
|
|
823
|
+
ctaHref: string | null
|
|
824
|
+
} {
|
|
825
|
+
if (!block || typeof block !== "object") {
|
|
826
|
+
return {
|
|
827
|
+
code: null,
|
|
828
|
+
message: null,
|
|
829
|
+
endAt: null,
|
|
830
|
+
active: undefined,
|
|
831
|
+
ctaLabel: null,
|
|
832
|
+
ctaHref: null,
|
|
833
|
+
}
|
|
380
834
|
}
|
|
835
|
+
const row = block as Record<string, unknown>
|
|
836
|
+
const code =
|
|
837
|
+
normalizeCmsText(row["promo-code"] ?? row.code ?? row["coupon-code"]) ?? null
|
|
838
|
+
const message =
|
|
839
|
+
normalizeCmsText(row["promo-text"] ?? row.message ?? row.text) ?? null
|
|
840
|
+
const endAtRaw = row["end-at"] ?? row.endAt ?? row["ends-at"]
|
|
841
|
+
const endAt = typeof endAtRaw === "string" && endAtRaw.trim() ? endAtRaw.trim() : null
|
|
842
|
+
const activeRaw = row["promo-active"] ?? row.active
|
|
843
|
+
const active = typeof activeRaw === "boolean" ? activeRaw : undefined
|
|
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 }
|
|
381
849
|
}
|
|
382
850
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
+
}
|
|
387
874
|
|
|
388
|
-
|
|
875
|
+
/** @deprecated Pass `pageInput` from main project. */
|
|
876
|
+
export const getPromoCountdownFromConfig = async (): Promise<{
|
|
877
|
+
code: string | null
|
|
878
|
+
message: string | null
|
|
879
|
+
endAt: string | null
|
|
880
|
+
active: boolean
|
|
881
|
+
ctaLabel: string | null
|
|
882
|
+
ctaHref: string | null
|
|
883
|
+
}> => {
|
|
884
|
+
const config = await getDynamicConfig()
|
|
885
|
+
return getPromoCountdownFromPageInput(config?.["homepage-config"] ?? null)
|
|
886
|
+
}
|
|
389
887
|
|
|
390
|
-
|
|
888
|
+
export type InstagramPostConfig = {
|
|
889
|
+
id: string
|
|
890
|
+
image: string
|
|
891
|
+
url?: string
|
|
892
|
+
}
|
|
391
893
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
894
|
+
export type InstagramPostsData = {
|
|
895
|
+
title: string
|
|
896
|
+
handle: string
|
|
897
|
+
profileUrl: string
|
|
898
|
+
profileImage?: string
|
|
899
|
+
posts: InstagramPostConfig[]
|
|
900
|
+
}
|
|
398
901
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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,
|
|
918
|
+
}
|
|
919
|
+
}
|
|
404
920
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
}
|
|
409
935
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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,
|
|
413
975
|
}
|
|
414
976
|
}
|
|
415
|
-
export const getPromoBarConfig = async (): Promise<{ text: string | null; code: string | null; value: string | null; active: boolean }> => {
|
|
416
|
-
try {
|
|
417
|
-
const config = await getDynamicConfig()
|
|
418
|
-
const promoConfig = config?.["homepage-config"]?.["promo-bar"]
|
|
419
977
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
+
}
|
|
983
|
+
|
|
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)
|
|
429
1019
|
}
|