medusa-storefront-data 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,11 @@
2
2
 
3
3
  import { cache } from "react"
4
4
  import { getAuthHeaders } from "../cookies"
5
+ import {
6
+ DEFAULT_HOMEPAGE_SECTIONS,
7
+ HOME_SECTION_COPY_ID_LIST,
8
+ type DefaultHomeSectionId,
9
+ } from "./homepage-section-defaults"
5
10
 
6
11
  // Define the structure based on the actual API response and client usage
7
12
  export interface DynamicConfig {
@@ -62,6 +67,8 @@ export interface DynamicConfig {
62
67
  "promo-value"?: string
63
68
  "promo-active"?: boolean
64
69
  }
70
+ /** Central homepage section copy (Option 1). Keys: camelCase or kebab-case section ids. */
71
+ sections?: Record<string, unknown>
65
72
  // Allow for other properties
66
73
  [key: string]: any
67
74
  }
@@ -253,32 +260,43 @@ export const getWebsiteDescriptionFromConfig = async (): Promise<string | null>
253
260
  }
254
261
  }
255
262
 
256
- export const getFeaturesFromConfig = async (): Promise<{ title: string; features: Array<{ name: string; icon: string }> } | null> => {
263
+ /** Why choose us defaults + `sections.whyChooseUs` override. */
264
+ export const getFeaturesFromConfig = async (): Promise<{
265
+ title: string
266
+ features: Array<{ name: string; icon: string }>
267
+ }> => {
257
268
  try {
258
- const config = await getDynamicConfig()
259
- const homepageConfig = config?.["homepage-config"]
260
-
261
- if (!homepageConfig) return null
262
-
263
- const title = homepageConfig["why-choose-us-title"] || "Why Choose Us?"
264
- const featuresRaw = homepageConfig["why-choose-us-features"] || []
265
-
266
- const features = featuresRaw.map((item: any) => {
267
- const feature = item?.feature || item
268
- return {
269
- name: feature?.["feature-name"] || "",
270
- icon: feature?.["feature-icon"] || "",
271
- }
272
- }).filter((f: any) => f.name && f.icon)
273
-
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
274
 
275
275
  return { title, features }
276
- } catch (error) {
277
-
278
- return null
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
+ }
279
283
  }
280
284
  }
281
285
 
286
+ function parseFeatureIconList(raw: unknown): Array<{ name: string; icon: string }> {
287
+ if (!Array.isArray(raw)) return []
288
+ return raw
289
+ .map((item: unknown) => {
290
+ if (!item || typeof item !== "object") return null
291
+ const feature = (item as Record<string, unknown>).feature ?? item
292
+ const row = feature as Record<string, unknown>
293
+ const name = normalizeCmsText(row["feature-name"] ?? row.name) ?? ""
294
+ const icon = normalizeCmsText(row["feature-icon"] ?? row.icon) ?? ""
295
+ return name && icon ? { name, icon } : null
296
+ })
297
+ .filter(Boolean) as Array<{ name: string; icon: string }>
298
+ }
299
+
282
300
  const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
283
301
 
284
302
  /** Fix common UTF-8 mojibake from CMS copy-paste (e.g. em dash, apostrophe). */
@@ -292,6 +310,197 @@ function normalizeCmsText(value: unknown): string | undefined {
292
310
  .replace(/≡ƒÿÄ≡ƒæòΓ£¿/g, " 👕✨")
293
311
  }
294
312
 
313
+ /** CMS copy for a homepage section (title, name, text, description, etc.). */
314
+ export type SectionCopy = {
315
+ title: string | null
316
+ name: string | null
317
+ text: string | null
318
+ description: string | null
319
+ subtitle: string | null
320
+ eyebrow: string | null
321
+ }
322
+
323
+ const EMPTY_SECTION_COPY: SectionCopy = {
324
+ title: null,
325
+ name: null,
326
+ text: null,
327
+ description: null,
328
+ subtitle: null,
329
+ eyebrow: null,
330
+ }
331
+
332
+ export function parseSectionCopy(block: unknown): SectionCopy {
333
+ if (!block || typeof block !== "object") return { ...EMPTY_SECTION_COPY }
334
+ const row = block as Record<string, unknown>
335
+ return {
336
+ title: normalizeCmsText(row.title) ?? null,
337
+ name: normalizeCmsText(row.name) ?? null,
338
+ text: normalizeCmsText(row.text) ?? null,
339
+ description: normalizeCmsText(row.description) ?? null,
340
+ subtitle: normalizeCmsText(row.subtitle) ?? null,
341
+ eyebrow: normalizeCmsText(row.eyebrow) ?? null,
342
+ }
343
+ }
344
+
345
+ export type HomeSectionCopyId = DefaultHomeSectionId
346
+
347
+ export type HomeSectionsCopy = Record<HomeSectionCopyId, SectionCopy>
348
+
349
+ const HOME_SECTION_COPY_IDS = new Set<HomeSectionCopyId>(HOME_SECTION_COPY_ID_LIST)
350
+
351
+ function sectionIdFromConfigKey(key: string): HomeSectionCopyId | null {
352
+ const id = key.replace(/-([a-z])/g, (_, c: string) =>
353
+ c.toUpperCase()
354
+ ) as HomeSectionCopyId
355
+ return HOME_SECTION_COPY_IDS.has(id) ? id : null
356
+ }
357
+
358
+ async function getHomepageSectionsRoot(): Promise<Record<string, unknown>> {
359
+ const config = await getDynamicConfig()
360
+ const root = config?.["homepage-config"]?.sections
361
+ return root && typeof root === "object"
362
+ ? (root as Record<string, unknown>)
363
+ : {}
364
+ }
365
+
366
+ function findSectionBlock(
367
+ root: Record<string, unknown>,
368
+ id: HomeSectionCopyId
369
+ ): Record<string, unknown> | null {
370
+ for (const [key, value] of Object.entries(root)) {
371
+ if (sectionIdFromConfigKey(key) !== id) continue
372
+ if (value && typeof value === "object") {
373
+ return value as Record<string, unknown>
374
+ }
375
+ if (typeof value === "string") {
376
+ return { text: value }
377
+ }
378
+ }
379
+ return null
380
+ }
381
+
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
+ }
413
+
414
+ async function getMergedSectionBlock(
415
+ id: HomeSectionCopyId
416
+ ): Promise<Record<string, unknown>> {
417
+ const root = await getHomepageSectionsRoot()
418
+ const defaults = DEFAULT_HOMEPAGE_SECTIONS[id] ?? {}
419
+ const override = findSectionBlock(root, id)
420
+ return mergeSectionBlock(
421
+ { ...defaults } as Record<string, unknown>,
422
+ override
423
+ )
424
+ }
425
+
426
+ function mergeSectionCopy(base: SectionCopy, patch: SectionCopy): SectionCopy {
427
+ const pick = (override: string | null, fallback: string | null) =>
428
+ override ?? fallback
429
+ return {
430
+ title: pick(patch.title ?? patch.name, base.title ?? base.name),
431
+ name: pick(patch.name, base.name),
432
+ text: pick(patch.text, base.text),
433
+ description: pick(patch.description, base.description),
434
+ subtitle: pick(patch.subtitle, base.subtitle),
435
+ eyebrow: pick(patch.eyebrow, base.eyebrow),
436
+ }
437
+ }
438
+
439
+ function parseAnnouncementMessages(block: Record<string, unknown> | null): string[] {
440
+ if (!block) return []
441
+ const raw = block.messages ?? block.items ?? block["announcement-messages"]
442
+ if (!Array.isArray(raw)) return []
443
+ return raw
444
+ .map((item: unknown) => {
445
+ if (typeof item === "string") return item.trim()
446
+ if (item && typeof item === "object") {
447
+ const row = item as Record<string, unknown>
448
+ const nested = (row.message ?? row.text) as unknown
449
+ return typeof nested === "string" ? nested.trim() : ""
450
+ }
451
+ return ""
452
+ })
453
+ .filter(Boolean)
454
+ }
455
+
456
+ /**
457
+ * Merged section copy: package defaults + `homepage-config.sections` overrides.
458
+ */
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
473
+ }
474
+ }
475
+
476
+ export type TrustFeaturesConfig = {
477
+ title: string | null
478
+ description: string | null
479
+ features: Array<{ name: string; icon: string }>
480
+ }
481
+
482
+ /** 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
+ }
502
+ }
503
+
295
504
  function normalizeBannerButtonName(value: unknown): string | undefined {
296
505
  const text = normalizeCmsText(value)
297
506
  if (!text) return undefined
@@ -328,6 +537,65 @@ export const getContactInfoFromConfig = async (): Promise<{ phone?: string; emai
328
537
  }
329
538
  }
330
539
 
540
+ function normalizeVideoStoryUrl(entry: unknown): string | null {
541
+ if (typeof entry === "string" && entry.trim()) return entry.trim()
542
+ if (!entry || typeof entry !== "object") return null
543
+ const row = entry as Record<string, unknown>
544
+ const nested =
545
+ (row["video-story"] as Record<string, unknown> | undefined) ??
546
+ (row["video_stories"] as Record<string, unknown> | undefined) ??
547
+ row
548
+ const url =
549
+ nested["video-url"] ??
550
+ nested["video_url"] ??
551
+ nested.url ??
552
+ nested.src ??
553
+ nested.video
554
+ return typeof url === "string" && url.trim() ? url.trim() : null
555
+ }
556
+
557
+ /**
558
+ * Homepage video stories/reviews carousel (`homepage-config.video-stories`).
559
+ */
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
+ }
596
+ }
597
+ }
598
+
331
599
  export const getTestimonialsFromConfig = async (): Promise<{ title: string; testimonials: Array<{ id: string; text: string; name: string; rating: number; avatar?: string }> } | null> => {
332
600
  try {
333
601
  const config = await getDynamicConfig()
@@ -335,26 +603,30 @@ export const getTestimonialsFromConfig = async (): Promise<{ title: string; test
335
603
 
336
604
  if (!homepageConfig) return null
337
605
 
338
- const title = homepageConfig["rating-title"] || "Happy Parents Say About Us"
606
+ const block = await getMergedSectionBlock("testimonials")
607
+ const copy = parseSectionCopy(block)
608
+ const title = copy.title ?? copy.name ?? ""
339
609
  const ratings = homepageConfig["ratings"] || []
340
610
 
341
- const testimonials = ratings.map((item: any, index: number) => {
342
- const review = item?.review || item
343
- return {
344
- id: `review-${index}`,
345
- text: review?.["review-description"] || "",
346
- name: review?.["review-user-name"] || "",
347
- rating: parseFloat(review?.["review-rating"] || "5"),
348
- avatar: review?.["review-profile-image"] || undefined,
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
+ }
349
622
  }
350
- }).filter((t: any) => t.text && t.name)
351
-
352
-
623
+ ).filter((t) => t.text && t.name)
353
624
 
354
625
  return { title, testimonials }
355
- } catch (error) {
356
-
357
- return null
626
+ } catch {
627
+ const block = DEFAULT_HOMEPAGE_SECTIONS.testimonials
628
+ const copy = parseSectionCopy(block)
629
+ return { title: copy.title ?? copy.name ?? "", testimonials: [] }
358
630
  }
359
631
  }
360
632
 
@@ -412,6 +684,212 @@ export const getFaqsFromConfig = async (): Promise<Array<{ category: string; ite
412
684
  return null
413
685
  }
414
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
+ }
696
+ }
697
+
698
+ export type AboutBrandConfig = {
699
+ eyebrow: string
700
+ title: string
701
+ description: string
702
+ readMoreHref: string
703
+ stats: Array<{ label: string; value: string }>
704
+ }
705
+
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
+ }
751
+ }
752
+ }
753
+
754
+ export type BrandPillarConfig = {
755
+ title: string
756
+ description: string
757
+ image?: string
758
+ }
759
+
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
+ }
791
+ }
792
+
793
+ export type BlogPostConfig = {
794
+ title: string
795
+ excerpt?: string
796
+ category?: string
797
+ date?: string
798
+ href: string
799
+ image?: string
800
+ }
801
+
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
+ }
839
+ }
840
+
841
+ function readPromoCountdownBlock(block: unknown): {
842
+ code: string | null
843
+ message: string | null
844
+ endAt: string | null
845
+ active: boolean | undefined
846
+ } {
847
+ if (!block || typeof block !== "object") {
848
+ return { code: null, message: null, endAt: null, active: undefined }
849
+ }
850
+ const row = block as Record<string, unknown>
851
+ const code =
852
+ normalizeCmsText(row["promo-code"] ?? row.code ?? row["coupon-code"]) ?? null
853
+ const message =
854
+ normalizeCmsText(row["promo-text"] ?? row.message ?? row.text) ?? null
855
+ const endAtRaw = row["end-at"] ?? row.endAt ?? row["ends-at"]
856
+ const endAt = typeof endAtRaw === "string" && endAtRaw.trim() ? endAtRaw.trim() : null
857
+ const activeRaw = row["promo-active"] ?? row.active
858
+ const active = typeof activeRaw === "boolean" ? activeRaw : undefined
859
+ return { code, message, endAt, active }
860
+ }
861
+
862
+ /** Promo countdown — defaults + `sections.promoCountdown` override. */
863
+ export const getPromoCountdownFromConfig = async (): Promise<{
864
+ code: string | null
865
+ message: string | null
866
+ endAt: string | null
867
+ 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
+ }
888
+ }
889
+ }
890
+
891
+ export { DEFAULT_HOMEPAGE_SECTIONS } from "./homepage-section-defaults"
892
+
415
893
  export const getPromoBarConfig = async (): Promise<{ text: string | null; code: string | null; value: string | null; active: boolean }> => {
416
894
  try {
417
895
  const config = await getDynamicConfig()