medusa-storefront-data 2.4.0 → 2.5.3

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 (214) hide show
  1. package/HOMEPAGE_CONFIG.md +113 -6
  2. package/package.json +6 -116
  3. package/src/server/brand-pillars-from-categories.ts +156 -0
  4. package/src/server/cart.ts +21 -21
  5. package/src/server/customer.ts +11 -11
  6. package/src/server/dynamic-config.ts +130 -181
  7. package/src/server/fulfillment.ts +3 -3
  8. package/src/server/help-faq-section-defaults.json +92 -0
  9. package/src/server/help-sections/help-faq.ts +25 -0
  10. package/src/server/help-sections/index.ts +8 -0
  11. package/src/server/home.ts +36 -211
  12. package/src/server/homepage-config.types.ts +24 -0
  13. package/src/server/homepage-section-defaults.ts +3 -0
  14. package/src/server/normalize-homepage-config.ts +279 -0
  15. package/src/server/orders.ts +6 -6
  16. package/src/server/page-input.ts +3 -1
  17. package/src/server/payment-details.ts +5 -5
  18. package/src/server/payment.ts +2 -2
  19. package/src/server/regions.ts +2 -2
  20. package/src/server/shoppable-looks.ts +1 -1
  21. package/src/util/store-client.ts +0 -3
  22. package/dist/segment-data/default-page-input.json +0 -191
  23. package/dist/src/config.d.ts +0 -3
  24. package/dist/src/config.d.ts.map +0 -1
  25. package/dist/src/config.js +0 -31
  26. package/dist/src/cookies.d.ts +0 -23
  27. package/dist/src/cookies.d.ts.map +0 -1
  28. package/dist/src/cookies.js +0 -140
  29. package/dist/src/edge.d.ts +0 -3
  30. package/dist/src/edge.d.ts.map +0 -1
  31. package/dist/src/edge.js +0 -1
  32. package/dist/src/middleware.d.ts +0 -3
  33. package/dist/src/middleware.d.ts.map +0 -1
  34. package/dist/src/middleware.js +0 -1
  35. package/dist/src/server/cart.d.ts +0 -96
  36. package/dist/src/server/cart.d.ts.map +0 -1
  37. package/dist/src/server/cart.js +0 -797
  38. package/dist/src/server/categories.d.ts +0 -4
  39. package/dist/src/server/categories.d.ts.map +0 -1
  40. package/dist/src/server/categories.js +0 -34
  41. package/dist/src/server/collections.d.ts +0 -8
  42. package/dist/src/server/collections.d.ts.map +0 -1
  43. package/dist/src/server/collections.js +0 -39
  44. package/dist/src/server/config-merge.d.ts +0 -3
  45. package/dist/src/server/config-merge.d.ts.map +0 -1
  46. package/dist/src/server/config-merge.js +0 -31
  47. package/dist/src/server/contact.d.ts +0 -34
  48. package/dist/src/server/contact.d.ts.map +0 -1
  49. package/dist/src/server/contact.js +0 -57
  50. package/dist/src/server/customer-registration.d.ts +0 -142
  51. package/dist/src/server/customer-registration.d.ts.map +0 -1
  52. package/dist/src/server/customer-registration.js +0 -295
  53. package/dist/src/server/customer.d.ts +0 -48
  54. package/dist/src/server/customer.d.ts.map +0 -1
  55. package/dist/src/server/customer.js +0 -413
  56. package/dist/src/server/dynamic-config.d.ts +0 -240
  57. package/dist/src/server/dynamic-config.d.ts.map +0 -1
  58. package/dist/src/server/dynamic-config.js +0 -703
  59. package/dist/src/server/fulfillment.d.ts +0 -5
  60. package/dist/src/server/fulfillment.d.ts.map +0 -1
  61. package/dist/src/server/fulfillment.js +0 -47
  62. package/dist/src/server/guest.d.ts +0 -81
  63. package/dist/src/server/guest.d.ts.map +0 -1
  64. package/dist/src/server/guest.js +0 -183
  65. package/dist/src/server/home-sections/about-brand.d.ts +0 -9
  66. package/dist/src/server/home-sections/about-brand.d.ts.map +0 -1
  67. package/dist/src/server/home-sections/about-brand.js +0 -9
  68. package/dist/src/server/home-sections/baptism-picks.d.ts +0 -10
  69. package/dist/src/server/home-sections/baptism-picks.d.ts.map +0 -1
  70. package/dist/src/server/home-sections/baptism-picks.js +0 -27
  71. package/dist/src/server/home-sections/baptism.d.ts +0 -13
  72. package/dist/src/server/home-sections/baptism.d.ts.map +0 -1
  73. package/dist/src/server/home-sections/baptism.js +0 -43
  74. package/dist/src/server/home-sections/blog-posts.d.ts +0 -9
  75. package/dist/src/server/home-sections/blog-posts.d.ts.map +0 -1
  76. package/dist/src/server/home-sections/blog-posts.js +0 -9
  77. package/dist/src/server/home-sections/brand-marquee.d.ts +0 -8
  78. package/dist/src/server/home-sections/brand-marquee.d.ts.map +0 -1
  79. package/dist/src/server/home-sections/brand-marquee.js +0 -7
  80. package/dist/src/server/home-sections/brand-pillars.d.ts +0 -9
  81. package/dist/src/server/home-sections/brand-pillars.d.ts.map +0 -1
  82. package/dist/src/server/home-sections/brand-pillars.js +0 -9
  83. package/dist/src/server/home-sections/category-pills.d.ts +0 -9
  84. package/dist/src/server/home-sections/category-pills.d.ts.map +0 -1
  85. package/dist/src/server/home-sections/category-pills.js +0 -19
  86. package/dist/src/server/home-sections/celebrity-trust.d.ts +0 -12
  87. package/dist/src/server/home-sections/celebrity-trust.d.ts.map +0 -1
  88. package/dist/src/server/home-sections/celebrity-trust.js +0 -27
  89. package/dist/src/server/home-sections/features.d.ts +0 -9
  90. package/dist/src/server/home-sections/features.d.ts.map +0 -1
  91. package/dist/src/server/home-sections/features.js +0 -9
  92. package/dist/src/server/home-sections/hero.d.ts +0 -9
  93. package/dist/src/server/home-sections/hero.d.ts.map +0 -1
  94. package/dist/src/server/home-sections/hero.js +0 -9
  95. package/dist/src/server/home-sections/instagram-posts.d.ts +0 -9
  96. package/dist/src/server/home-sections/instagram-posts.d.ts.map +0 -1
  97. package/dist/src/server/home-sections/instagram-posts.js +0 -9
  98. package/dist/src/server/home-sections/loved-by-moms.d.ts +0 -12
  99. package/dist/src/server/home-sections/loved-by-moms.d.ts.map +0 -1
  100. package/dist/src/server/home-sections/loved-by-moms.js +0 -20
  101. package/dist/src/server/home-sections/luxe-favourites.d.ts +0 -11
  102. package/dist/src/server/home-sections/luxe-favourites.d.ts.map +0 -1
  103. package/dist/src/server/home-sections/luxe-favourites.js +0 -19
  104. package/dist/src/server/home-sections/new-arrivals-classic.d.ts +0 -8
  105. package/dist/src/server/home-sections/new-arrivals-classic.d.ts.map +0 -1
  106. package/dist/src/server/home-sections/new-arrivals-classic.js +0 -10
  107. package/dist/src/server/home-sections/new-arrivals.d.ts +0 -12
  108. package/dist/src/server/home-sections/new-arrivals.d.ts.map +0 -1
  109. package/dist/src/server/home-sections/new-arrivals.js +0 -28
  110. package/dist/src/server/home-sections/promo-announcements.d.ts +0 -7
  111. package/dist/src/server/home-sections/promo-announcements.d.ts.map +0 -1
  112. package/dist/src/server/home-sections/promo-announcements.js +0 -7
  113. package/dist/src/server/home-sections/promo-countdown.d.ts +0 -6
  114. package/dist/src/server/home-sections/promo-countdown.d.ts.map +0 -1
  115. package/dist/src/server/home-sections/promo-countdown.js +0 -5
  116. package/dist/src/server/home-sections/shared.d.ts +0 -10
  117. package/dist/src/server/home-sections/shared.d.ts.map +0 -1
  118. package/dist/src/server/home-sections/shared.js +0 -29
  119. package/dist/src/server/home-sections/shop-by-age.d.ts +0 -9
  120. package/dist/src/server/home-sections/shop-by-age.d.ts.map +0 -1
  121. package/dist/src/server/home-sections/shop-by-age.js +0 -17
  122. package/dist/src/server/home-sections/shop-by-category.d.ts +0 -10
  123. package/dist/src/server/home-sections/shop-by-category.d.ts.map +0 -1
  124. package/dist/src/server/home-sections/shop-by-category.js +0 -17
  125. package/dist/src/server/home-sections/testimonials.d.ts +0 -9
  126. package/dist/src/server/home-sections/testimonials.d.ts.map +0 -1
  127. package/dist/src/server/home-sections/testimonials.js +0 -9
  128. package/dist/src/server/home-sections/theme-dresses.d.ts +0 -12
  129. package/dist/src/server/home-sections/theme-dresses.d.ts.map +0 -1
  130. package/dist/src/server/home-sections/theme-dresses.js +0 -27
  131. package/dist/src/server/home-sections/video-stories.d.ts +0 -9
  132. package/dist/src/server/home-sections/video-stories.d.ts.map +0 -1
  133. package/dist/src/server/home-sections/video-stories.js +0 -9
  134. package/dist/src/server/home-sections/why-choose-us.d.ts +0 -9
  135. package/dist/src/server/home-sections/why-choose-us.d.ts.map +0 -1
  136. package/dist/src/server/home-sections/why-choose-us.js +0 -9
  137. package/dist/src/server/home.d.ts +0 -62
  138. package/dist/src/server/home.d.ts.map +0 -1
  139. package/dist/src/server/home.js +0 -122
  140. package/dist/src/server/homepage-config.types.d.ts +0 -274
  141. package/dist/src/server/homepage-config.types.d.ts.map +0 -1
  142. package/dist/src/server/homepage-config.types.js +0 -33
  143. package/dist/src/server/homepage-section-defaults.d.ts +0 -188
  144. package/dist/src/server/homepage-section-defaults.d.ts.map +0 -1
  145. package/dist/src/server/homepage-section-defaults.js +0 -188
  146. package/dist/src/server/index.d.ts +0 -25
  147. package/dist/src/server/index.d.ts.map +0 -1
  148. package/dist/src/server/index.js +0 -24
  149. package/dist/src/server/locale-actions.d.ts +0 -14
  150. package/dist/src/server/locale-actions.d.ts.map +0 -1
  151. package/dist/src/server/locale-actions.js +0 -58
  152. package/dist/src/server/locales.d.ts +0 -8
  153. package/dist/src/server/locales.d.ts.map +0 -1
  154. package/dist/src/server/locales.js +0 -12
  155. package/dist/src/server/notifications.d.ts +0 -2
  156. package/dist/src/server/notifications.d.ts.map +0 -1
  157. package/dist/src/server/notifications.js +0 -20
  158. package/dist/src/server/onboarding.d.ts +0 -2
  159. package/dist/src/server/onboarding.d.ts.map +0 -1
  160. package/dist/src/server/onboarding.js +0 -8
  161. package/dist/src/server/orders.d.ts +0 -63
  162. package/dist/src/server/orders.d.ts.map +0 -1
  163. package/dist/src/server/orders.js +0 -230
  164. package/dist/src/server/page-input.d.ts +0 -19
  165. package/dist/src/server/page-input.d.ts.map +0 -1
  166. package/dist/src/server/page-input.js +0 -40
  167. package/dist/src/server/payment-details.d.ts +0 -5
  168. package/dist/src/server/payment-details.d.ts.map +0 -1
  169. package/dist/src/server/payment-details.js +0 -28
  170. package/dist/src/server/payment.d.ts +0 -3
  171. package/dist/src/server/payment.d.ts.map +0 -1
  172. package/dist/src/server/payment.js +0 -13
  173. package/dist/src/server/pincode.d.ts +0 -7
  174. package/dist/src/server/pincode.d.ts.map +0 -1
  175. package/dist/src/server/pincode.js +0 -30
  176. package/dist/src/server/products.d.ts +0 -54
  177. package/dist/src/server/products.d.ts.map +0 -1
  178. package/dist/src/server/products.js +0 -154
  179. package/dist/src/server/regions.d.ts +0 -5
  180. package/dist/src/server/regions.d.ts.map +0 -1
  181. package/dist/src/server/regions.js +0 -57
  182. package/dist/src/server/resolve-home-segment-data.d.ts +0 -25
  183. package/dist/src/server/resolve-home-segment-data.d.ts.map +0 -1
  184. package/dist/src/server/resolve-home-segment-data.js +0 -20
  185. package/dist/src/server/returns.d.ts +0 -29
  186. package/dist/src/server/returns.d.ts.map +0 -1
  187. package/dist/src/server/returns.js +0 -132
  188. package/dist/src/server/shoppable-looks.d.ts +0 -17
  189. package/dist/src/server/shoppable-looks.d.ts.map +0 -1
  190. package/dist/src/server/shoppable-looks.js +0 -136
  191. package/dist/src/server/swaps.d.ts +0 -15
  192. package/dist/src/server/swaps.d.ts.map +0 -1
  193. package/dist/src/server/swaps.js +0 -89
  194. package/dist/src/server/variants.d.ts +0 -3
  195. package/dist/src/server/variants.d.ts.map +0 -1
  196. package/dist/src/server/variants.js +0 -15
  197. package/dist/src/server/wishlist.d.ts +0 -11
  198. package/dist/src/server/wishlist.d.ts.map +0 -1
  199. package/dist/src/server/wishlist.js +0 -49
  200. package/dist/src/util/get-locale-header.d.ts +0 -4
  201. package/dist/src/util/get-locale-header.d.ts.map +0 -1
  202. package/dist/src/util/get-locale-header.js +0 -7
  203. package/dist/src/util/medusa-error.d.ts +0 -2
  204. package/dist/src/util/medusa-error.d.ts.map +0 -1
  205. package/dist/src/util/medusa-error.js +0 -18
  206. package/dist/src/util/revalidate-cart.d.ts +0 -2
  207. package/dist/src/util/revalidate-cart.d.ts.map +0 -1
  208. package/dist/src/util/revalidate-cart.js +0 -8
  209. package/dist/src/util/sort-products.d.ts +0 -3
  210. package/dist/src/util/sort-products.d.ts.map +0 -1
  211. package/dist/src/util/sort-products.js +0 -1
  212. package/dist/src/util/store-client.d.ts +0 -13
  213. package/dist/src/util/store-client.d.ts.map +0 -1
  214. package/dist/src/util/store-client.js +0 -77
@@ -0,0 +1,25 @@
1
+ import "server-only"
2
+
3
+ import {
4
+ getHelpFaqFromPageInput,
5
+ type HelpFaqCategory,
6
+ type HelpFaqConfig,
7
+ type HelpFaqSectionCopy,
8
+ } from "../dynamic-config"
9
+ import type { HomepageConfig } from "../homepage-config.types"
10
+
11
+ export type { HelpFaqCategory, HelpFaqConfig, HelpFaqSectionCopy }
12
+
13
+ export type HelpFaqSectionData = HelpFaqConfig
14
+
15
+ export type HelpFaqSectionLoaderOptions = {
16
+ pageInput?: HomepageConfig | null
17
+ }
18
+
19
+ /** Help page FAQ block — `sections.helpFaq` + legacy `faq-array`. */
20
+ export async function loadHelpFaqSectionData(
21
+ _countryCode: string,
22
+ options?: HelpFaqSectionLoaderOptions
23
+ ): Promise<HelpFaqSectionData> {
24
+ return getHelpFaqFromPageInput(options?.pageInput ?? null)
25
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ loadHelpFaqSectionData,
3
+ type HelpFaqCategory,
4
+ type HelpFaqConfig,
5
+ type HelpFaqSectionCopy,
6
+ type HelpFaqSectionData,
7
+ type HelpFaqSectionLoaderOptions,
8
+ } from "./help-faq"
@@ -1,11 +1,4 @@
1
- import "server-only";
2
-
3
- import { HttpTypes } from "@medusajs/types";
4
- import { listCollections } from "./collections";
5
- import { listCategories } from "./categories";
6
- import { listProductsWithSort, getProductsByTag, listProducts } from "./products";
7
- import { getRegion } from "./regions";
8
- import type { HomepageConfig } from "./homepage-config.types";
1
+ import type { HttpTypes } from "@medusajs/types"
9
2
  import type {
10
3
  InstagramPostsData,
11
4
  AboutBrandConfig,
@@ -14,212 +7,44 @@ import type {
14
7
  HomeSectionsCopy,
15
8
  TrustFeaturesConfig,
16
9
  HomepageBanner,
17
- } from "./dynamic-config";
18
- import { fetchRatings } from "medusa-reviews-logic/server";
19
- import {
20
- getBaptismPicksLooks,
21
- type ShoppableLook,
22
- } from "./shoppable-looks";
23
- import { resolveHomeSegmentConfigData } from "./resolve-home-segment-data";
24
-
25
- async function productsForCategoryIds(
26
- countryCode: string,
27
- categoryIds: string[],
28
- limit: number
29
- ): Promise<HttpTypes.StoreProduct[]> {
30
- if (!categoryIds.length) return [];
31
- try {
32
- const { response } = await listProducts({
33
- countryCode,
34
- queryParams: {
35
- category_id: categoryIds,
36
- limit,
37
- } as HttpTypes.FindParams & HttpTypes.StoreProductListParams,
38
- });
39
- return response.products ?? [];
40
- } catch {
41
- return [];
42
- }
43
- }
44
-
45
- function categoryIdsMatching(
46
- categories: HttpTypes.StoreProductCategory[],
47
- pattern: RegExp
48
- ): string[] {
49
- return categories
50
- .filter((c) => pattern.test((c.name || c.handle || "").toLowerCase()))
51
- .map((c) => c.id)
52
- .filter(Boolean) as string[];
53
- }
10
+ } from "./dynamic-config"
11
+ import type { ShoppableLook } from "./shoppable-looks"
54
12
 
13
+ /** Shape returned by the legacy `loadHomePageData` loader (removed — use per-section loaders). */
55
14
  export type HomePageData = {
56
- region: Awaited<ReturnType<typeof getRegion>>;
57
- collections: Array<Record<string, unknown>>;
58
- categories: HttpTypes.StoreProductCategory[];
59
- newArrivals: HttpTypes.StoreProduct[];
60
- bestsellers: HttpTypes.StoreProduct[];
61
- testimonials: unknown | null;
62
- ratings: unknown[];
63
- videoStories: { title: string; videoUrls: string[] } | null;
64
- announcementMessages: string[];
15
+ region: HttpTypes.StoreRegion | null
16
+ collections: Array<Record<string, unknown>>
17
+ categories: HttpTypes.StoreProductCategory[]
18
+ newArrivals: HttpTypes.StoreProduct[]
19
+ bestsellers: HttpTypes.StoreProduct[]
20
+ testimonials: unknown | null
21
+ ratings: unknown[]
22
+ videoStories: { title: string; videoUrls: string[] } | null
23
+ announcementMessages: string[]
65
24
  promoCountdown: {
66
- code: string | null;
67
- message: string | null;
68
- endAt: string | null;
69
- active: boolean;
70
- ctaLabel: string | null;
71
- ctaHref: string | null;
72
- };
73
- luxeFavourites: HttpTypes.StoreProduct[];
74
- celebrityProduct: HttpTypes.StoreProduct | null;
75
- baptismGirl: HttpTypes.StoreProduct[];
76
- baptismBoy: HttpTypes.StoreProduct[];
77
- baptismPicks: ShoppableLook[];
78
- themeProducts: HttpTypes.StoreProduct[];
79
- aboutBrand: AboutBrandConfig;
80
- brandPillars: { sectionTitle: string; pillars: BrandPillarConfig[] };
81
- blogPosts: BlogPostConfig[];
82
- instagramPosts: InstagramPostsData;
83
- sectionsCopy: HomeSectionsCopy;
84
- trustFeatures: TrustFeaturesConfig;
85
- homeBanners: HomepageBanner[] | null;
86
- appBanners: HomepageBanner[] | null;
25
+ code: string | null
26
+ message: string | null
27
+ endAt: string | null
28
+ active: boolean
29
+ ctaLabel: string | null
30
+ ctaHref: string | null
31
+ }
32
+ luxeFavourites: HttpTypes.StoreProduct[]
33
+ celebrityProduct: HttpTypes.StoreProduct | null
34
+ baptismGirl: HttpTypes.StoreProduct[]
35
+ baptismBoy: HttpTypes.StoreProduct[]
36
+ baptismPicks: ShoppableLook[]
37
+ themeProducts: HttpTypes.StoreProduct[]
38
+ aboutBrand: AboutBrandConfig
39
+ brandPillars: { sectionTitle: string; pillars: BrandPillarConfig[] }
40
+ blogPosts: BlogPostConfig[]
41
+ instagramPosts: InstagramPostsData
42
+ sectionsCopy: HomeSectionsCopy
43
+ trustFeatures: TrustFeaturesConfig
44
+ homeBanners: HomepageBanner[] | null
45
+ appBanners: HomepageBanner[] | null
87
46
  whyChooseUsFeatures: {
88
- title: string;
89
- features: Array<{ name: string; icon: string }>;
90
- };
91
- };
92
-
93
- export type LoadHomePageDataOptions = {
94
- /** Merged `homepage-config` from main project (fetched via dynamic-config API there). */
95
- pageInput?: HomepageConfig | null;
96
- };
97
-
98
- /**
99
- * Parallel home page data loader. Catalog APIs run here; segment copy comes from
100
- * `pageInput` (or package JSON defaults) — no dynamic-config fetch in this package.
101
- */
102
- export async function loadHomePageData(
103
- countryCode: string,
104
- options?: LoadHomePageDataOptions
105
- ): Promise<HomePageData | null> {
106
- const pageInput = options?.pageInput ?? null;
107
- const segmentConfig = resolveHomeSegmentConfigData(pageInput);
108
-
109
- const region = await getRegion(countryCode);
110
- if (!region) return null;
111
-
112
- const [
113
- collectionsResult,
114
- categoriesResult,
115
- newArrivalsResult,
116
- bestsellersResult,
117
- allRatings,
118
- themeByTag,
119
- baptismByTag,
120
- ] = await Promise.all([
121
- listCollections({ fields: "id, handle, title, metadata" }).catch(() => ({
122
- collections: [],
123
- })),
124
- listCategories({
125
- limit: 100,
126
- fields:
127
- "id,handle,name,metadata,parent_category_id,*parent_category,*parent_category.parent_category",
128
- }).catch(() => []),
129
- listProductsWithSort({
130
- page: 1,
131
- queryParams: { limit: 12 },
132
- sortBy: "created_at",
133
- countryCode,
134
- }).catch(() => ({ response: { products: [] } })),
135
- getProductsByTag({
136
- tagValue: "bestseller",
137
- limit: 10,
138
- countryCode,
139
- }).catch(() => []),
140
- fetchRatings(),
141
- getProductsByTag({ tagValue: "theme", limit: 8, countryCode }).catch(() => []),
142
- getProductsByTag({ tagValue: "baptism", limit: 12, countryCode }).catch(() => []),
143
- ]);
144
-
145
- const categories = (categoriesResult ?? []) as HttpTypes.StoreProductCategory[];
146
- const collections =
147
- (collectionsResult as { collections?: unknown[] }).collections ?? [];
148
- const newArrivals =
149
- (newArrivalsResult as { response?: { products?: HttpTypes.StoreProduct[] } })
150
- .response?.products ?? [];
151
- const bestsellers = (bestsellersResult ?? []) as HttpTypes.StoreProduct[];
152
-
153
- const girlIds = categoryIdsMatching(categories, /baptism.*girl|girl.*baptism|^girl$/);
154
- const boyIds = categoryIdsMatching(categories, /baptism.*boy|boy.*baptism|^boy$/);
155
- const baptismIds = categoryIdsMatching(categories, /baptism/);
156
-
157
- const [baptismGirl, baptismBoy] = await Promise.all([
158
- girlIds.length
159
- ? productsForCategoryIds(countryCode, girlIds, 8)
160
- : baptismByTag.filter((p) => /girl/i.test(p.title || "")).slice(0, 8),
161
- boyIds.length
162
- ? productsForCategoryIds(countryCode, boyIds, 8)
163
- : baptismByTag.filter((p) => /boy/i.test(p.title || "")).slice(0, 8),
164
- ]);
165
-
166
- let baptismFallback: HttpTypes.StoreProduct[] = [];
167
- if (baptismGirl.length === 0 && baptismBoy.length === 0) {
168
- baptismFallback =
169
- baptismIds.length > 0
170
- ? await productsForCategoryIds(countryCode, baptismIds, 12)
171
- : baptismByTag;
47
+ title: string
48
+ features: Array<{ name: string; icon: string }>
172
49
  }
173
-
174
- const resolvedBaptismGirl =
175
- baptismGirl.length > 0 ? baptismGirl : baptismFallback.slice(0, 8);
176
- const resolvedBaptismBoy =
177
- baptismBoy.length > 0 ? baptismBoy : baptismFallback.slice(0, 8);
178
-
179
- const themeProducts =
180
- themeByTag.length > 0
181
- ? themeByTag
182
- : await productsForCategoryIds(
183
- countryCode,
184
- categoryIdsMatching(categories, /theme/),
185
- 8
186
- );
187
-
188
- const baptismPicksFallback = (baptismByTag.length ? baptismByTag : baptismFallback).slice(
189
- 0,
190
- 8
191
- );
192
- const baptismPicks = await getBaptismPicksLooks(
193
- countryCode,
194
- baptismPicksFallback,
195
- pageInput
196
- );
197
-
198
- return {
199
- region,
200
- collections: collections as Array<Record<string, unknown>>,
201
- categories,
202
- newArrivals,
203
- bestsellers,
204
- testimonials: segmentConfig.testimonials,
205
- ratings: allRatings ?? [],
206
- videoStories: segmentConfig.videoStories,
207
- announcementMessages: segmentConfig.announcementMessages,
208
- promoCountdown: segmentConfig.promoCountdown,
209
- luxeFavourites: bestsellers.slice(0, 4),
210
- celebrityProduct: bestsellers[0] ?? newArrivals[0] ?? null,
211
- baptismGirl: resolvedBaptismGirl,
212
- baptismBoy: resolvedBaptismBoy,
213
- baptismPicks,
214
- themeProducts,
215
- aboutBrand: segmentConfig.aboutBrand,
216
- brandPillars: segmentConfig.brandPillars,
217
- blogPosts: segmentConfig.blogPosts,
218
- instagramPosts: segmentConfig.instagramPosts,
219
- sectionsCopy: segmentConfig.sectionsCopy,
220
- trustFeatures: segmentConfig.trustFeatures,
221
- homeBanners: segmentConfig.homeBanners,
222
- appBanners: segmentConfig.appBanners,
223
- whyChooseUsFeatures: segmentConfig.whyChooseUsFeatures,
224
- };
225
50
  }
@@ -138,6 +138,28 @@ export type HomepageBlogPostsSection = HomepageSectionCopyFields & {
138
138
  "blog-posts"?: HomepageBlogPost[]
139
139
  }
140
140
 
141
+ export type HomepageHelpFaqItem = {
142
+ question?: string
143
+ answer?: string
144
+ q?: string
145
+ a?: string
146
+ faq?: HomepageHelpFaqItem
147
+ }
148
+
149
+ export type HomepageHelpFaqCategory = {
150
+ category?: string
151
+ name?: string
152
+ icon?: string
153
+ color?: string
154
+ items?: HomepageHelpFaqItem[]
155
+ faqs?: HomepageHelpFaqItem[]
156
+ }
157
+
158
+ export type HomepageHelpFaqSection = HomepageSectionCopyFields & {
159
+ titleHighlight?: string
160
+ categories?: HomepageHelpFaqCategory[]
161
+ }
162
+
141
163
  export type HomepageInstagramPost = {
142
164
  id?: string
143
165
  image?: string
@@ -203,6 +225,7 @@ export type HomepageSectionsConfig = {
203
225
  lovedByMoms?: HomepageSectionCopyFields
204
226
  videoStories?: HomepageVideoStoriesSection
205
227
  blogPosts?: HomepageBlogPostsSection
228
+ helpFaq?: HomepageHelpFaqSection
206
229
  /** Allow kebab-case keys from legacy CMS */
207
230
  [sectionKey: string]: HomepageSectionCopyFields | undefined
208
231
  }
@@ -330,6 +353,7 @@ export const HOMEPAGE_SECTION_IDS = [
330
353
  "lovedByMoms",
331
354
  "videoStories",
332
355
  "blogPosts",
356
+ "helpFaq",
333
357
  ] as const
334
358
 
335
359
  export type HomepageSectionId = (typeof HOMEPAGE_SECTION_IDS)[number]
@@ -1,3 +1,5 @@
1
+ import helpFaqDefaults from "./help-faq-section-defaults.json"
2
+
1
3
  /**
2
4
  * Default `homepage-config.sections` blocks.
3
5
  * Dynamic config overrides any field; unset fields keep these defaults.
@@ -161,6 +163,7 @@ export const DEFAULT_HOMEPAGE_SECTIONS = {
161
163
  title: "Watch Stories & Reviews",
162
164
  "video-urls": [],
163
165
  },
166
+ helpFaq: helpFaqDefaults as Record<string, unknown>,
164
167
  blogPosts: {
165
168
  title: "Blog posts",
166
169
  posts: [
@@ -0,0 +1,279 @@
1
+ import type { HomepageConfig } from "./homepage-config.types"
2
+
3
+ type Loose = Record<string, unknown>
4
+
5
+ function asObject(v: unknown): Loose | null {
6
+ return v && typeof v === "object" && !Array.isArray(v) ? (v as Loose) : null
7
+ }
8
+
9
+ function asArray(v: unknown): unknown[] {
10
+ return Array.isArray(v) ? v : []
11
+ }
12
+
13
+ /** Map simplified banner row → legacy `homepage-banner` / `app-banner` entry. */
14
+ function legacyBannerEntry(
15
+ row: unknown,
16
+ prefix: "homepage-banner" | "app-banner"
17
+ ): Loose | null {
18
+ const r = asObject(row)
19
+ if (!r) return null
20
+ const image = r.image ?? r[`${prefix}-image`]
21
+ if (!image) return null
22
+ return {
23
+ [prefix]: {
24
+ [`${prefix}-image`]: image,
25
+ [`${prefix}-title`]: r.title ?? r[`${prefix}-title`],
26
+ [`${prefix}-subtitle`]: r.subtitle ?? r[`${prefix}-subtitle`],
27
+ [`${prefix}-description`]: r.description ?? r[`${prefix}-description`],
28
+ [`${prefix}-button-name`]: r.buttonText ?? r.buttonName ?? r[`${prefix}-button-name`],
29
+ [`${prefix}-button-link`]: r.buttonLink ?? r[`${prefix}-button-link`],
30
+ ...(prefix === "app-banner" && r.link != null
31
+ ? { "app-banner-link": r.link }
32
+ : {}),
33
+ },
34
+ }
35
+ }
36
+
37
+ function normalizeBanners(site: Loose | null): {
38
+ desktop: Loose[]
39
+ mobile: Loose[]
40
+ } {
41
+ const banners = asObject(site?.banners) ?? asObject(site)
42
+ const desktopRaw =
43
+ banners?.desktop ?? banners?.["homepage-banner-array"] ?? site?.["homepage-banner-array"]
44
+ const mobileRaw = banners?.mobile ?? banners?.["app-banner-array"] ?? site?.["app-banner-array"]
45
+
46
+ const desktop = asArray(desktopRaw)
47
+ .map((row) => legacyBannerEntry(row, "homepage-banner"))
48
+ .filter(Boolean) as Loose[]
49
+
50
+ const mobile = asArray(mobileRaw)
51
+ .map((row) => legacyBannerEntry(row, "app-banner"))
52
+ .filter(Boolean) as Loose[]
53
+
54
+ return { desktop, mobile }
55
+ }
56
+
57
+ function normalizeContact(site: Loose | null): Loose | undefined {
58
+ const c = asObject(site?.contact) ?? asObject(site?.["contact-us"])
59
+ if (!c) return undefined
60
+ return {
61
+ "contact-phone": c.phone ?? c["contact-phone"],
62
+ "contact-email": c.email ?? c["contact-email"],
63
+ "contact-whatsapp": c.whatsapp ?? c["contact-whatsapp"],
64
+ "contact-address": c.address ?? c["contact-address"],
65
+ }
66
+ }
67
+
68
+ function normalizeSocial(site: Loose | null): Loose[] {
69
+ const raw = site?.social ?? site?.["social-links"]
70
+ return asArray(raw).map((item) => {
71
+ const row = asObject((item as Loose)?.["social-link"]) ?? asObject(item)
72
+ if (!row) return null
73
+ return {
74
+ "social-link": {
75
+ "social-platform-name": row.name ?? row["social-platform-name"],
76
+ "social-platform-url": row.url ?? row["social-platform-url"],
77
+ "social-platform-icon": row.icon ?? row["social-platform-icon"],
78
+ },
79
+ }
80
+ }).filter(Boolean) as Loose[]
81
+ }
82
+
83
+ function normalizeFaqs(site: Loose | null): Loose[] {
84
+ const raw = site?.faqs ?? site?.["faq-array"]
85
+ return asArray(raw).map((item) => {
86
+ const f = asObject((item as Loose)?.faq) ?? asObject(item)
87
+ if (!f) return null
88
+ return {
89
+ faq: {
90
+ "faq-category": f.category ?? f["faq-category"],
91
+ "faq-question": f.question ?? f["faq-question"],
92
+ "faq-answer": f.answer ?? f["faq-answer"],
93
+ },
94
+ }
95
+ }).filter(Boolean) as Loose[]
96
+ }
97
+
98
+ function normalizeTestimonialItem(item: unknown): Loose | null {
99
+ const r = asObject((item as Loose)?.testimonial) ?? asObject(item)
100
+ if (!r) return null
101
+ const text = String(r.text ?? r.description ?? "").trim()
102
+ const name = String(r.name ?? "").trim()
103
+ if (!text || !name) return null
104
+ return { text, name, avatar: r.avatar, rating: r.rating }
105
+ }
106
+
107
+ function normalizeSections(sections: Loose): Loose {
108
+ const out: Loose = { ...sections }
109
+
110
+ const promoAnnouncements = asObject(sections.promoAnnouncements)
111
+ if (promoAnnouncements?.messages) {
112
+ const messages = asArray(promoAnnouncements.messages)
113
+ .map((m) => {
114
+ if (typeof m === "string") return m.trim()
115
+ const row = asObject(m)
116
+ return String(row?.message ?? row?.text ?? "").trim()
117
+ })
118
+ .filter(Boolean)
119
+ out.promoAnnouncements = { ...promoAnnouncements, messages }
120
+ }
121
+
122
+ const testimonials = asObject(sections.testimonials)
123
+ if (testimonials) {
124
+ const items =
125
+ testimonials.items ??
126
+ testimonials.testimonials ??
127
+ testimonials.reviews
128
+ if (Array.isArray(items)) {
129
+ out.testimonials = {
130
+ ...testimonials,
131
+ items: items.map(normalizeTestimonialItem).filter(Boolean),
132
+ }
133
+ }
134
+ }
135
+
136
+ const promo = asObject(sections.promoCountdown)
137
+ if (promo) {
138
+ out.promoCountdown = {
139
+ code: promo.code ?? promo["promo-code"],
140
+ text: promo.text ?? promo.message ?? promo["promo-text"],
141
+ "end-at": promo.endAt ?? promo["end-at"],
142
+ active: promo.active ?? promo["promo-active"],
143
+ "cta-label": promo.ctaLabel ?? promo["cta-label"],
144
+ "cta-href": promo.ctaHref ?? promo["cta-href"],
145
+ }
146
+ }
147
+
148
+ const baptism = asObject(sections.baptismPicks)
149
+ if (baptism?.looks) {
150
+ out.baptismPicks = {
151
+ title: baptism.title,
152
+ looks: asArray(baptism.looks).map((look) => {
153
+ const L = asObject((look as Loose)?.look) ?? asObject(look)
154
+ if (!L) return null
155
+ return {
156
+ image: L.image,
157
+ "product-ids": L.productIds ?? L["product-ids"],
158
+ "product-handles": L.productHandles ?? L["product-handles"],
159
+ }
160
+ }).filter(Boolean),
161
+ }
162
+ }
163
+
164
+ const ig = asObject(sections.instagramPosts)
165
+ if (ig) {
166
+ out.instagramPosts = {
167
+ title: ig.title,
168
+ handle: ig.handle,
169
+ "profile-url": ig.profileUrl ?? ig["profile-url"],
170
+ "profile-image": ig.profileImage ?? ig["profile-image"],
171
+ posts: asArray(ig.posts).map((p) => {
172
+ const row = asObject((p as Loose)?.post) ?? asObject(p)
173
+ return row ? { image: row.image, url: row.url ?? row.link } : null
174
+ }).filter(Boolean),
175
+ }
176
+ }
177
+
178
+ const about = asObject(sections.aboutBrand)
179
+ if (about) {
180
+ out.aboutBrand = {
181
+ eyebrow: about.eyebrow,
182
+ title: about.title,
183
+ description: about.description ?? about.text,
184
+ readMoreHref: about.readMoreHref ?? about["read-more-link"],
185
+ stats: about.stats,
186
+ }
187
+ }
188
+
189
+ // brandPillars: title only in simplified CMS (cards from categories in storefront)
190
+ const pillars = asObject(sections.brandPillars)
191
+ if (pillars && !pillars.pillars) {
192
+ out.brandPillars = {
193
+ title: pillars.title,
194
+ description: pillars.description,
195
+ }
196
+ }
197
+
198
+ const helpFaq = asObject(sections.helpFaq)
199
+ if (helpFaq) {
200
+ const categories = asArray(helpFaq.categories)
201
+ .map((row) => {
202
+ const cat = asObject((row as Loose)?.category) ?? asObject(row)
203
+ if (!cat) return null
204
+ const items = asArray(cat.items)
205
+ .map((item) => {
206
+ const faq = asObject((item as Loose)?.faq) ?? asObject(item)
207
+ if (!faq) return null
208
+ const question = String(
209
+ faq.question ?? faq["faq-question"] ?? ""
210
+ ).trim()
211
+ const answer = String(faq.answer ?? faq["faq-answer"] ?? "").trim()
212
+ if (!question || !answer) return null
213
+ return { question, answer }
214
+ })
215
+ .filter(Boolean)
216
+ const category = String(cat.category ?? cat.name ?? "").trim()
217
+ if (!category || items.length === 0) return null
218
+ return {
219
+ category,
220
+ icon: cat.icon,
221
+ color: cat.color,
222
+ items,
223
+ }
224
+ })
225
+ .filter(Boolean)
226
+ if (categories.length) {
227
+ out.helpFaq = {
228
+ eyebrow: helpFaq.eyebrow,
229
+ title: helpFaq.title,
230
+ titleHighlight: helpFaq.titleHighlight,
231
+ subtitle: helpFaq.subtitle ?? helpFaq.description,
232
+ categories,
233
+ }
234
+ }
235
+ }
236
+
237
+ return out
238
+ }
239
+
240
+ /**
241
+ * Accepts simplified `homepage-config` (site / banners / sections) or legacy shape.
242
+ * Storefront always receives the legacy `HomepageConfig` it already parses.
243
+ */
244
+ export function normalizeHomepageConfig(input: unknown): HomepageConfig {
245
+ if (!input || typeof input !== "object") return {}
246
+
247
+ const raw = input as Loose
248
+ const site = asObject(raw.site)
249
+ const hasSimplifiedSite = Boolean(site)
250
+
251
+ if (!hasSimplifiedSite) {
252
+ return raw as HomepageConfig
253
+ }
254
+
255
+ const { desktop, mobile } = normalizeBanners(raw)
256
+ const sections = normalizeSections(asObject(raw.sections) ?? {})
257
+
258
+ const legacy: HomepageConfig = {
259
+ logo: (site?.logo as string) ?? (raw.logo as string),
260
+ "website-description":
261
+ (site?.description as string) ??
262
+ (raw["website-description"] as string),
263
+ sections: sections as HomepageConfig["sections"],
264
+ }
265
+
266
+ if (desktop.length) legacy["homepage-banner-array"] = desktop as never
267
+ if (mobile.length) legacy["app-banner-array"] = mobile as never
268
+
269
+ const contact = normalizeContact(site)
270
+ if (contact) legacy["contact-us"] = contact as never
271
+
272
+ const social = normalizeSocial(site)
273
+ if (social.length) legacy["social-links"] = social as never
274
+
275
+ const faqs = normalizeFaqs(site)
276
+ if (faqs.length) legacy["faq-array"] = faqs as never
277
+
278
+ return legacy
279
+ }
@@ -32,7 +32,7 @@ import {
32
32
  } from "medusa-services/cart"
33
33
  import {
34
34
  getGuestStoreClientOptions,
35
- getStoreCartClientOptions,
35
+ getStoreClientOptions,
36
36
  getStoreClientOptionsWithToken,
37
37
  } from "../util/store-client"
38
38
 
@@ -63,7 +63,7 @@ export const retrieveOrder = async (id: string) => {
63
63
  return order as unknown as HttpTypes.StoreOrder
64
64
  }
65
65
 
66
- const options = await getStoreCartClientOptions()
66
+ const options = await getStoreClientOptions()
67
67
  const { order } = await medusaOrderRetrieve(id, options, {
68
68
  fields: MEDUSA_ORDER_DETAIL_FIELDS,
69
69
  })
@@ -79,7 +79,7 @@ export const listOrders = async (
79
79
  filters?: Record<string, any>
80
80
  ) => {
81
81
  try {
82
- const options = await getStoreCartClientOptions()
82
+ const options = await getStoreClientOptions()
83
83
  const { orders } = await medusaOrderList(options, {
84
84
  limit,
85
85
  offset,
@@ -111,7 +111,7 @@ export const createTransferRequest = async (
111
111
  }
112
112
 
113
113
  try {
114
- const options = await getStoreCartClientOptions()
114
+ const options = await getStoreClientOptions()
115
115
  const { order } = await medusaOrderRequestTransfer(id, options, {
116
116
  fields: "id, email",
117
117
  })
@@ -127,7 +127,7 @@ export const createTransferRequest = async (
127
127
 
128
128
  export const acceptTransferRequest = async (id: string, token: string) => {
129
129
  try {
130
- const options = await getStoreCartClientOptions()
130
+ const options = await getStoreClientOptions()
131
131
  const { order } = await medusaOrderAcceptTransfer(id, token, options)
132
132
  return {
133
133
  success: true,
@@ -141,7 +141,7 @@ export const acceptTransferRequest = async (id: string, token: string) => {
141
141
 
142
142
  export const declineTransferRequest = async (id: string, token: string) => {
143
143
  try {
144
- const options = await getStoreCartClientOptions()
144
+ const options = await getStoreClientOptions()
145
145
  const { order } = await medusaOrderDeclineTransfer(id, token, options)
146
146
  return {
147
147
  success: true,