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.
Files changed (269) hide show
  1. package/HOMEPAGE_CONFIG.md +99 -0
  2. package/dist/segment-data/default-page-input.json +191 -0
  3. package/dist/src/config.d.ts.map +1 -0
  4. package/dist/src/cookies.d.ts.map +1 -0
  5. package/dist/src/edge.d.ts.map +1 -0
  6. package/dist/src/middleware.d.ts.map +1 -0
  7. package/dist/src/server/cart.d.ts.map +1 -0
  8. package/dist/src/server/categories.d.ts.map +1 -0
  9. package/dist/src/server/collections.d.ts.map +1 -0
  10. package/dist/src/server/config-merge.d.ts +3 -0
  11. package/dist/src/server/config-merge.d.ts.map +1 -0
  12. package/dist/src/server/config-merge.js +31 -0
  13. package/dist/src/server/contact.d.ts.map +1 -0
  14. package/dist/src/server/customer-registration.d.ts.map +1 -0
  15. package/dist/src/server/customer.d.ts.map +1 -0
  16. package/dist/src/server/dynamic-config.d.ts +240 -0
  17. package/dist/src/server/dynamic-config.d.ts.map +1 -0
  18. package/dist/src/server/dynamic-config.js +703 -0
  19. package/dist/src/server/fulfillment.d.ts.map +1 -0
  20. package/dist/src/server/guest.d.ts.map +1 -0
  21. package/dist/src/server/home-sections/about-brand.d.ts +9 -0
  22. package/dist/src/server/home-sections/about-brand.d.ts.map +1 -0
  23. package/dist/src/server/home-sections/about-brand.js +9 -0
  24. package/dist/src/server/home-sections/baptism-picks.d.ts +10 -0
  25. package/dist/src/server/home-sections/baptism-picks.d.ts.map +1 -0
  26. package/dist/src/server/home-sections/baptism-picks.js +27 -0
  27. package/dist/src/server/home-sections/baptism.d.ts +13 -0
  28. package/dist/src/server/home-sections/baptism.d.ts.map +1 -0
  29. package/dist/src/server/home-sections/baptism.js +43 -0
  30. package/dist/src/server/home-sections/blog-posts.d.ts +9 -0
  31. package/dist/src/server/home-sections/blog-posts.d.ts.map +1 -0
  32. package/dist/src/server/home-sections/blog-posts.js +9 -0
  33. package/dist/src/server/home-sections/brand-marquee.d.ts +8 -0
  34. package/dist/src/server/home-sections/brand-marquee.d.ts.map +1 -0
  35. package/dist/src/server/home-sections/brand-marquee.js +7 -0
  36. package/dist/src/server/home-sections/brand-pillars.d.ts +9 -0
  37. package/dist/src/server/home-sections/brand-pillars.d.ts.map +1 -0
  38. package/dist/src/server/home-sections/brand-pillars.js +9 -0
  39. package/dist/src/server/home-sections/category-pills.d.ts +9 -0
  40. package/dist/src/server/home-sections/category-pills.d.ts.map +1 -0
  41. package/dist/src/server/home-sections/category-pills.js +19 -0
  42. package/dist/src/server/home-sections/celebrity-trust.d.ts +12 -0
  43. package/dist/src/server/home-sections/celebrity-trust.d.ts.map +1 -0
  44. package/dist/src/server/home-sections/celebrity-trust.js +27 -0
  45. package/dist/src/server/home-sections/features.d.ts +9 -0
  46. package/dist/src/server/home-sections/features.d.ts.map +1 -0
  47. package/dist/src/server/home-sections/features.js +9 -0
  48. package/dist/src/server/home-sections/hero.d.ts +9 -0
  49. package/dist/src/server/home-sections/hero.d.ts.map +1 -0
  50. package/dist/src/server/home-sections/hero.js +9 -0
  51. package/dist/src/server/home-sections/instagram-posts.d.ts +9 -0
  52. package/dist/src/server/home-sections/instagram-posts.d.ts.map +1 -0
  53. package/dist/src/server/home-sections/instagram-posts.js +9 -0
  54. package/dist/src/server/home-sections/loved-by-moms.d.ts +12 -0
  55. package/dist/src/server/home-sections/loved-by-moms.d.ts.map +1 -0
  56. package/dist/src/server/home-sections/loved-by-moms.js +20 -0
  57. package/dist/src/server/home-sections/luxe-favourites.d.ts +11 -0
  58. package/dist/src/server/home-sections/luxe-favourites.d.ts.map +1 -0
  59. package/dist/src/server/home-sections/luxe-favourites.js +19 -0
  60. package/dist/src/server/home-sections/new-arrivals-classic.d.ts +8 -0
  61. package/dist/src/server/home-sections/new-arrivals-classic.d.ts.map +1 -0
  62. package/dist/src/server/home-sections/new-arrivals-classic.js +10 -0
  63. package/dist/src/server/home-sections/new-arrivals.d.ts +12 -0
  64. package/dist/src/server/home-sections/new-arrivals.d.ts.map +1 -0
  65. package/dist/src/server/home-sections/new-arrivals.js +28 -0
  66. package/dist/src/server/home-sections/promo-announcements.d.ts +7 -0
  67. package/dist/src/server/home-sections/promo-announcements.d.ts.map +1 -0
  68. package/dist/src/server/home-sections/promo-announcements.js +7 -0
  69. package/dist/src/server/home-sections/promo-countdown.d.ts +6 -0
  70. package/dist/src/server/home-sections/promo-countdown.d.ts.map +1 -0
  71. package/dist/src/server/home-sections/promo-countdown.js +5 -0
  72. package/dist/src/server/home-sections/shared.d.ts +10 -0
  73. package/dist/src/server/home-sections/shared.d.ts.map +1 -0
  74. package/dist/src/server/home-sections/shared.js +29 -0
  75. package/dist/src/server/home-sections/shop-by-age.d.ts +9 -0
  76. package/dist/src/server/home-sections/shop-by-age.d.ts.map +1 -0
  77. package/dist/src/server/home-sections/shop-by-age.js +17 -0
  78. package/dist/src/server/home-sections/shop-by-category.d.ts +10 -0
  79. package/dist/src/server/home-sections/shop-by-category.d.ts.map +1 -0
  80. package/dist/src/server/home-sections/shop-by-category.js +17 -0
  81. package/dist/src/server/home-sections/testimonials.d.ts +9 -0
  82. package/dist/src/server/home-sections/testimonials.d.ts.map +1 -0
  83. package/dist/src/server/home-sections/testimonials.js +9 -0
  84. package/dist/src/server/home-sections/theme-dresses.d.ts +12 -0
  85. package/dist/src/server/home-sections/theme-dresses.d.ts.map +1 -0
  86. package/dist/src/server/home-sections/theme-dresses.js +27 -0
  87. package/dist/src/server/home-sections/video-stories.d.ts +9 -0
  88. package/dist/src/server/home-sections/video-stories.d.ts.map +1 -0
  89. package/dist/src/server/home-sections/video-stories.js +9 -0
  90. package/dist/src/server/home-sections/why-choose-us.d.ts +9 -0
  91. package/dist/src/server/home-sections/why-choose-us.d.ts.map +1 -0
  92. package/dist/src/server/home-sections/why-choose-us.js +9 -0
  93. package/dist/src/server/home.d.ts +62 -0
  94. package/dist/src/server/home.d.ts.map +1 -0
  95. package/dist/{server → src/server}/home.js +29 -31
  96. package/dist/src/server/homepage-config.types.d.ts +274 -0
  97. package/dist/src/server/homepage-config.types.d.ts.map +1 -0
  98. package/dist/src/server/homepage-config.types.js +33 -0
  99. package/dist/{server → src/server}/homepage-section-defaults.d.ts +35 -2
  100. package/dist/src/server/homepage-section-defaults.d.ts.map +1 -0
  101. package/dist/{server → src/server}/homepage-section-defaults.js +41 -2
  102. package/dist/{server → src/server}/index.d.ts +2 -0
  103. package/dist/src/server/index.d.ts.map +1 -0
  104. package/dist/{server → src/server}/index.js +2 -0
  105. package/dist/src/server/locale-actions.d.ts.map +1 -0
  106. package/dist/src/server/locales.d.ts.map +1 -0
  107. package/dist/src/server/notifications.d.ts.map +1 -0
  108. package/dist/src/server/onboarding.d.ts.map +1 -0
  109. package/dist/src/server/orders.d.ts.map +1 -0
  110. package/dist/src/server/page-input.d.ts +19 -0
  111. package/dist/src/server/page-input.d.ts.map +1 -0
  112. package/dist/src/server/page-input.js +40 -0
  113. package/dist/src/server/payment-details.d.ts.map +1 -0
  114. package/dist/src/server/payment.d.ts.map +1 -0
  115. package/dist/src/server/pincode.d.ts.map +1 -0
  116. package/dist/src/server/products.d.ts.map +1 -0
  117. package/dist/src/server/regions.d.ts.map +1 -0
  118. package/dist/src/server/resolve-home-segment-data.d.ts +25 -0
  119. package/dist/src/server/resolve-home-segment-data.d.ts.map +1 -0
  120. package/dist/src/server/resolve-home-segment-data.js +20 -0
  121. package/dist/src/server/returns.d.ts.map +1 -0
  122. package/dist/src/server/shoppable-looks.d.ts +17 -0
  123. package/dist/src/server/shoppable-looks.d.ts.map +1 -0
  124. package/dist/src/server/shoppable-looks.js +136 -0
  125. package/dist/src/server/swaps.d.ts.map +1 -0
  126. package/dist/src/server/variants.d.ts.map +1 -0
  127. package/dist/src/server/wishlist.d.ts.map +1 -0
  128. package/dist/src/util/get-locale-header.d.ts.map +1 -0
  129. package/dist/src/util/medusa-error.d.ts.map +1 -0
  130. package/dist/src/util/revalidate-cart.d.ts.map +1 -0
  131. package/dist/src/util/sort-products.d.ts.map +1 -0
  132. package/dist/src/util/store-client.d.ts.map +1 -0
  133. package/homepage-config.example.json +209 -0
  134. package/homepage-sections.example.json +55 -0
  135. package/package.json +121 -2
  136. package/segment-data/default-page-input.json +191 -0
  137. package/src/server/config-merge.ts +31 -0
  138. package/src/server/dynamic-config.ts +620 -508
  139. package/src/server/home-sections/about-brand.ts +24 -0
  140. package/src/server/home-sections/baptism-picks.ts +46 -0
  141. package/src/server/home-sections/baptism.ts +64 -0
  142. package/src/server/home-sections/blog-posts.ts +23 -0
  143. package/src/server/home-sections/brand-marquee.ts +17 -0
  144. package/src/server/home-sections/brand-pillars.ts +23 -0
  145. package/src/server/home-sections/category-pills.ts +32 -0
  146. package/src/server/home-sections/celebrity-trust.ts +42 -0
  147. package/src/server/home-sections/features.ts +23 -0
  148. package/src/server/home-sections/hero.ts +24 -0
  149. package/src/server/home-sections/instagram-posts.ts +23 -0
  150. package/src/server/home-sections/loved-by-moms.ts +34 -0
  151. package/src/server/home-sections/luxe-favourites.ts +30 -0
  152. package/src/server/home-sections/new-arrivals-classic.ts +21 -0
  153. package/src/server/home-sections/new-arrivals.ts +47 -0
  154. package/src/server/home-sections/promo-announcements.ts +17 -0
  155. package/src/server/home-sections/promo-countdown.ts +15 -0
  156. package/src/server/home-sections/shared.ts +44 -0
  157. package/src/server/home-sections/shop-by-age.ts +26 -0
  158. package/src/server/home-sections/shop-by-category.ts +28 -0
  159. package/src/server/home-sections/testimonials.ts +23 -0
  160. package/src/server/home-sections/theme-dresses.ts +49 -0
  161. package/src/server/home-sections/video-stories.ts +23 -0
  162. package/src/server/home-sections/why-choose-us.ts +23 -0
  163. package/src/server/home.ts +68 -54
  164. package/src/server/homepage-config.types.ts +335 -0
  165. package/src/server/homepage-section-defaults.ts +41 -2
  166. package/src/server/index.ts +2 -0
  167. package/src/server/page-input.ts +64 -0
  168. package/src/server/resolve-home-segment-data.ts +63 -0
  169. package/src/server/shoppable-looks.ts +178 -0
  170. package/dist/config.d.ts.map +0 -1
  171. package/dist/cookies.d.ts.map +0 -1
  172. package/dist/edge.d.ts.map +0 -1
  173. package/dist/middleware.d.ts.map +0 -1
  174. package/dist/server/cart.d.ts.map +0 -1
  175. package/dist/server/categories.d.ts.map +0 -1
  176. package/dist/server/collections.d.ts.map +0 -1
  177. package/dist/server/contact.d.ts.map +0 -1
  178. package/dist/server/customer-registration.d.ts.map +0 -1
  179. package/dist/server/customer.d.ts.map +0 -1
  180. package/dist/server/dynamic-config.d.ts +0 -202
  181. package/dist/server/dynamic-config.d.ts.map +0 -1
  182. package/dist/server/dynamic-config.js +0 -675
  183. package/dist/server/fulfillment.d.ts.map +0 -1
  184. package/dist/server/guest.d.ts.map +0 -1
  185. package/dist/server/home.d.ts +0 -42
  186. package/dist/server/home.d.ts.map +0 -1
  187. package/dist/server/homepage-section-defaults.d.ts.map +0 -1
  188. package/dist/server/index.d.ts.map +0 -1
  189. package/dist/server/locale-actions.d.ts.map +0 -1
  190. package/dist/server/locales.d.ts.map +0 -1
  191. package/dist/server/notifications.d.ts.map +0 -1
  192. package/dist/server/onboarding.d.ts.map +0 -1
  193. package/dist/server/orders.d.ts.map +0 -1
  194. package/dist/server/payment-details.d.ts.map +0 -1
  195. package/dist/server/payment.d.ts.map +0 -1
  196. package/dist/server/pincode.d.ts.map +0 -1
  197. package/dist/server/products.d.ts.map +0 -1
  198. package/dist/server/regions.d.ts.map +0 -1
  199. package/dist/server/returns.d.ts.map +0 -1
  200. package/dist/server/swaps.d.ts.map +0 -1
  201. package/dist/server/variants.d.ts.map +0 -1
  202. package/dist/server/wishlist.d.ts.map +0 -1
  203. package/dist/util/get-locale-header.d.ts.map +0 -1
  204. package/dist/util/medusa-error.d.ts.map +0 -1
  205. package/dist/util/revalidate-cart.d.ts.map +0 -1
  206. package/dist/util/sort-products.d.ts.map +0 -1
  207. package/dist/util/store-client.d.ts.map +0 -1
  208. /package/dist/{config.d.ts → src/config.d.ts} +0 -0
  209. /package/dist/{config.js → src/config.js} +0 -0
  210. /package/dist/{cookies.d.ts → src/cookies.d.ts} +0 -0
  211. /package/dist/{cookies.js → src/cookies.js} +0 -0
  212. /package/dist/{edge.d.ts → src/edge.d.ts} +0 -0
  213. /package/dist/{edge.js → src/edge.js} +0 -0
  214. /package/dist/{middleware.d.ts → src/middleware.d.ts} +0 -0
  215. /package/dist/{middleware.js → src/middleware.js} +0 -0
  216. /package/dist/{server → src/server}/cart.d.ts +0 -0
  217. /package/dist/{server → src/server}/cart.js +0 -0
  218. /package/dist/{server → src/server}/categories.d.ts +0 -0
  219. /package/dist/{server → src/server}/categories.js +0 -0
  220. /package/dist/{server → src/server}/collections.d.ts +0 -0
  221. /package/dist/{server → src/server}/collections.js +0 -0
  222. /package/dist/{server → src/server}/contact.d.ts +0 -0
  223. /package/dist/{server → src/server}/contact.js +0 -0
  224. /package/dist/{server → src/server}/customer-registration.d.ts +0 -0
  225. /package/dist/{server → src/server}/customer-registration.js +0 -0
  226. /package/dist/{server → src/server}/customer.d.ts +0 -0
  227. /package/dist/{server → src/server}/customer.js +0 -0
  228. /package/dist/{server → src/server}/fulfillment.d.ts +0 -0
  229. /package/dist/{server → src/server}/fulfillment.js +0 -0
  230. /package/dist/{server → src/server}/guest.d.ts +0 -0
  231. /package/dist/{server → src/server}/guest.js +0 -0
  232. /package/dist/{server → src/server}/locale-actions.d.ts +0 -0
  233. /package/dist/{server → src/server}/locale-actions.js +0 -0
  234. /package/dist/{server → src/server}/locales.d.ts +0 -0
  235. /package/dist/{server → src/server}/locales.js +0 -0
  236. /package/dist/{server → src/server}/notifications.d.ts +0 -0
  237. /package/dist/{server → src/server}/notifications.js +0 -0
  238. /package/dist/{server → src/server}/onboarding.d.ts +0 -0
  239. /package/dist/{server → src/server}/onboarding.js +0 -0
  240. /package/dist/{server → src/server}/orders.d.ts +0 -0
  241. /package/dist/{server → src/server}/orders.js +0 -0
  242. /package/dist/{server → src/server}/payment-details.d.ts +0 -0
  243. /package/dist/{server → src/server}/payment-details.js +0 -0
  244. /package/dist/{server → src/server}/payment.d.ts +0 -0
  245. /package/dist/{server → src/server}/payment.js +0 -0
  246. /package/dist/{server → src/server}/pincode.d.ts +0 -0
  247. /package/dist/{server → src/server}/pincode.js +0 -0
  248. /package/dist/{server → src/server}/products.d.ts +0 -0
  249. /package/dist/{server → src/server}/products.js +0 -0
  250. /package/dist/{server → src/server}/regions.d.ts +0 -0
  251. /package/dist/{server → src/server}/regions.js +0 -0
  252. /package/dist/{server → src/server}/returns.d.ts +0 -0
  253. /package/dist/{server → src/server}/returns.js +0 -0
  254. /package/dist/{server → src/server}/swaps.d.ts +0 -0
  255. /package/dist/{server → src/server}/swaps.js +0 -0
  256. /package/dist/{server → src/server}/variants.d.ts +0 -0
  257. /package/dist/{server → src/server}/variants.js +0 -0
  258. /package/dist/{server → src/server}/wishlist.d.ts +0 -0
  259. /package/dist/{server → src/server}/wishlist.js +0 -0
  260. /package/dist/{util → src/util}/get-locale-header.d.ts +0 -0
  261. /package/dist/{util → src/util}/get-locale-header.js +0 -0
  262. /package/dist/{util → src/util}/medusa-error.d.ts +0 -0
  263. /package/dist/{util → src/util}/medusa-error.js +0 -0
  264. /package/dist/{util → src/util}/revalidate-cart.d.ts +0 -0
  265. /package/dist/{util → src/util}/revalidate-cart.js +0 -0
  266. /package/dist/{util → src/util}/sort-products.d.ts +0 -0
  267. /package/dist/{util → src/util}/sort-products.js +0 -0
  268. /package/dist/{util → src/util}/store-client.d.ts +0 -0
  269. /package/dist/{util → src/util}/store-client.js +0 -0
@@ -1,4 +1,4 @@
1
- "use server"
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
- // Define the structure based on the actual API response and client usage
12
- export interface DynamicConfig {
13
- "homepage-config"?: {
14
- logo?: string
15
- "homepage-banner-array"?: Array<{
16
- "homepage-banner"?: {
17
- "homepage-banner-image"?: string
18
- "homepage-banner-title"?: string
19
- "homepage-banner-subtitle"?: string
20
- "homepage-banner-description"?: string
21
- "homepage-banner-button-name"?: string
22
- "homepage-banner-button-link"?: string
23
- }
24
- }>
25
- "app-banner-array"?: Array<{
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 const getLogoFromConfig = async (): Promise<string | null> => {
127
- try {
128
- const config = await getDynamicConfig()
129
- const logo = config?.["homepage-config"]?.logo
130
-
131
- return logo || null
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
- return null
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 const getHomeBannersFromConfig = async (): Promise<Array<{
180
- image?: string;
181
- title?: string;
182
- subtitle?: string;
183
- description?: string;
184
- buttonName?: string;
185
- buttonLink?: string;
186
- }> | null> => {
187
- try {
188
- const config = await getDynamicConfig()
189
- const bannerArray = config?.["homepage-config"]?.["homepage-banner-array"]
190
-
191
- if (bannerArray && bannerArray.length > 0) {
192
- return bannerArray.map(item => {
193
- const banner = item["homepage-banner"]
194
- return {
195
- image: banner?.["homepage-banner-image"],
196
- title: normalizeCmsText(banner?.["homepage-banner-title"]),
197
- subtitle: normalizeCmsText(banner?.["homepage-banner-subtitle"]),
198
- description: normalizeCmsText(banner?.["homepage-banner-description"]),
199
- buttonName: normalizeBannerButtonName(banner?.["homepage-banner-button-name"]),
200
- buttonLink: banner?.["homepage-banner-button-link"],
201
- }
202
- })
203
- }
204
- return null
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 const getAppBannersFromConfig = async (): Promise<Array<{
212
- image?: string;
213
- title?: string;
214
- subtitle?: string;
215
- description?: string;
216
- buttonName?: string;
217
- buttonLink?: string;
218
- }> | null> => {
219
- try {
220
- const config = await getDynamicConfig()
221
- const bannerArray = config?.["homepage-config"]?.["app-banner-array"]
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
- if (bannerArray && bannerArray.length > 0) {
224
- return bannerArray.map(item => {
225
- const banner = item["app-banner"]
226
- return {
227
- image: banner?.["app-banner-image"],
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
- return null
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 const getWebsiteDescriptionFromConfig = async (): Promise<string | null> => {
250
- try {
251
- const config = await getDynamicConfig()
252
- const homepageConfig = config?.["homepage-config"]
253
- const desc = homepageConfig?.["website-description"] || homepageConfig?.["website_description"]
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
- return null
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
- try {
269
- const block = await getMergedSectionBlock("whyChooseUs")
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
- async function getHomepageSectionsRoot(): Promise<Record<string, unknown>> {
359
- const config = await getDynamicConfig()
360
- const root = config?.["homepage-config"]?.sections
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
- /** Merge CMS section block onto package defaults (override wins when set). */
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
- async function getMergedSectionBlock(
415
- id: HomeSectionCopyId
416
- ): Promise<Record<string, unknown>> {
417
- const root = await getHomepageSectionsRoot()
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 const getHomeSectionsCopyFromConfig = async (): Promise<HomeSectionsCopy> => {
460
- try {
461
- const copy = {} as HomeSectionsCopy
462
- for (const id of HOME_SECTION_COPY_ID_LIST) {
463
- const block = await getMergedSectionBlock(id)
464
- copy[id] = parseSectionCopy(block)
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
- try {
485
- const block = await getMergedSectionBlock("features")
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 const getContactInfoFromConfig = async (): Promise<{ phone?: string; email?: string; address?: string } | null> => {
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
- return null
525
- }
526
-
527
-
528
-
529
- return {
530
- phone: contactConfig["contact-phone"],
531
- email: normalizeConfigEmail(contactConfig["contact-email"]),
532
- address: contactConfig["contact-address"],
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
- try {
565
- const config = await getDynamicConfig()
566
- const homepageConfig = config?.["homepage-config"]
567
- if (!homepageConfig) return null
568
-
569
- const block = await getMergedSectionBlock("videoStories")
570
- const copy = parseSectionCopy(block)
571
- const title = copy.title ?? copy.name ?? ""
572
-
573
- const raw =
574
- block["video-urls"] ??
575
- block.videoUrls ??
576
- block.videos ??
577
- block["video-stories"] ??
578
- homepageConfig["video-stories"] ??
579
- homepageConfig["video-stories-array"] ??
580
- homepageConfig["video_stories"] ??
581
- []
582
-
583
- const list = Array.isArray(raw) ? raw : [raw]
584
- const videoUrls = list
585
- .map(normalizeVideoStoryUrl)
586
- .filter((url): url is string => Boolean(url))
587
-
588
- return { title, videoUrls }
589
- } catch {
590
- const block = DEFAULT_HOMEPAGE_SECTIONS.videoStories
591
- const copy = parseSectionCopy(block)
592
- return {
593
- title: copy.title ?? copy.name ?? "",
594
- videoUrls: [],
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
- export const getTestimonialsFromConfig = async (): Promise<{ title: string; testimonials: Array<{ id: string; text: string; name: string; rating: number; avatar?: string }> } | null> => {
600
- try {
601
- const config = await getDynamicConfig()
602
- const homepageConfig = config?.["homepage-config"]
603
-
604
- if (!homepageConfig) return null
605
-
606
- const block = await getMergedSectionBlock("testimonials")
607
- const copy = parseSectionCopy(block)
608
- const title = copy.title ?? copy.name ?? ""
609
- const ratings = homepageConfig["ratings"] || []
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
- let testimonials = (Array.isArray(ratings) ? ratings : []).map(
612
- (item: unknown, index: number) => {
613
- const row = item as Record<string, unknown>
614
- const review = (row?.review ?? row) as Record<string, unknown>
615
- return {
616
- id: `review-${index}`,
617
- text: String(review?.["review-description"] ?? "").trim(),
618
- name: String(review?.["review-user-name"] ?? "").trim(),
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
- return { title, testimonials }
626
- } catch {
627
- const block = DEFAULT_HOMEPAGE_SECTIONS.testimonials
628
- const copy = parseSectionCopy(block)
629
- return { title: copy.title ?? copy.name ?? "", testimonials: [] }
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
- export const getSocialLinksFromConfig = async (): Promise<Array<{ name: string; url: string; icon?: string }> | null> => {
634
- try {
635
- const config = await getDynamicConfig()
636
- const socialLinksRaw = config?.["homepage-config"]?.["social-links"] || []
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
- const socialLinks = socialLinksRaw.map((item: any) => {
639
- const link = item?.["social-link"] || item
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"] || undefined,
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
- }).filter((l: any) => l.name && l.url)
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
- export const getFaqsFromConfig = async (): Promise<Array<{ category: string; items: Array<{ q: string; a: string }> }> | null> => {
656
- try {
657
- const config = await getDynamicConfig()
658
- const faqsRaw = config?.["homepage-config"]?.["faq-array"] || []
659
-
660
- if (!Array.isArray(faqsRaw) || faqsRaw.length === 0) return null
661
-
662
- const categoriesMap: Record<string, { displayCategory: string, items: Array<{q: string; a: string}> }> = {}
663
-
664
- faqsRaw.forEach((item: any) => {
665
- const faq = item?.["faq"] || item
666
- const displayCategory = (faq?.["faq-category"] || "General Questions").trim()
667
- const categoryKey = displayCategory.toLowerCase()
668
- const q = (faq?.["faq-question"] || "").trim()
669
- const a = (faq?.["faq-answer"] || "").trim()
670
-
671
- if (q && a) {
672
- if (!categoriesMap[categoryKey]) categoriesMap[categoryKey] = { displayCategory, items: [] }
673
- categoriesMap[categoryKey].items.push({ q, a })
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
- const faqs = Object.values(categoriesMap).map(catData => ({
678
- category: catData.displayCategory,
679
- items: catData.items
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
- return faqs.length > 0 ? faqs : null
683
- } catch (error) {
684
- return null
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
- try {
689
- const block = await getMergedSectionBlock("promoAnnouncements")
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 const getAboutBrandFromConfig = async (): Promise<AboutBrandConfig> => {
707
- try {
708
- const config = await getDynamicConfig()
709
- const merged = await getMergedSectionBlock("aboutBrand")
710
- const legacy =
711
- config?.["homepage-config"]?.["about-brand"] ??
712
- config?.["homepage-config"]?.["who-we-are"]
713
- const b = mergeSectionBlock(
714
- merged,
715
- legacy && typeof legacy === "object"
716
- ? (legacy as Record<string, unknown>)
717
- : null
718
- )
719
-
720
- const statsRaw = (b.stats ?? b["about-stats"] ?? []) as unknown[]
721
- const stats = Array.isArray(statsRaw)
722
- ? statsRaw
723
- .map((s: unknown) => {
724
- if (!s || typeof s !== "object") return null
725
- const row = s as Record<string, unknown>
726
- const label = String(row.label ?? row.name ?? "").trim()
727
- const value = String(row.value ?? "").trim()
728
- return label && value ? { label, value } : null
729
- })
730
- .filter(Boolean) as Array<{ label: string; value: string }>
731
- : []
732
-
733
- const copy = parseSectionCopy(b)
734
- return {
735
- eyebrow: copy.eyebrow ?? "",
736
- title: copy.title ?? copy.name ?? "",
737
- description: copy.description ?? copy.text ?? "",
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
- try {
765
- const block = await getMergedSectionBlock("brandPillars")
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
- try {
804
- const block = await getMergedSectionBlock("blogPosts")
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 { code: null, message: null, endAt: null, active: undefined }
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
- return { code, message, endAt, active }
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
- try {
870
- const block = await getMergedSectionBlock("promoCountdown")
871
- const fromBlock = readPromoCountdownBlock(block)
872
- const defaults = readPromoCountdownBlock(DEFAULT_HOMEPAGE_SECTIONS.promoCountdown)
884
+ const config = await getDynamicConfig()
885
+ return getPromoCountdownFromPageInput(config?.["homepage-config"] ?? null)
886
+ }
873
887
 
874
- return {
875
- code: fromBlock.code ?? defaults.code,
876
- message: fromBlock.message ?? defaults.message,
877
- endAt: fromBlock.endAt ?? defaults.endAt,
878
- active: fromBlock.active ?? defaults.active ?? false,
879
- }
880
- } catch {
881
- const defaults = readPromoCountdownBlock(DEFAULT_HOMEPAGE_SECTIONS.promoCountdown)
882
- return {
883
- code: defaults.code,
884
- message: defaults.message,
885
- endAt: defaults.endAt,
886
- active: defaults.active ?? false,
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
- export { DEFAULT_HOMEPAGE_SECTIONS } from "./homepage-section-defaults"
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
- export const getPromoBarConfig = async (): Promise<{ text: string | null; code: string | null; value: string | null; active: boolean }> => {
894
- try {
895
- const config = await getDynamicConfig()
896
- const promoConfig = config?.["homepage-config"]?.["promo-bar"]
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
- return {
899
- text: normalizeCmsText(promoConfig?.["promo-text"]) || null,
900
- code: promoConfig?.["promo-code"] || null,
901
- value: promoConfig?.["promo-value"] || null,
902
- active: promoConfig?.["promo-active"] ?? false,
903
- }
904
- } catch (error) {
905
- return { text: null, code: null, value: null, active: false }
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
  }