medusa-storefront-data 2.3.0 → 2.5.1

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