medusa-storefront-data 2.0.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.
- package/dist/server/customer.d.ts +1 -1
- package/dist/server/customer.d.ts.map +1 -1
- package/dist/server/customer.js +1 -0
- package/dist/server/dynamic-config.d.ts +78 -1
- package/dist/server/dynamic-config.d.ts.map +1 -1
- package/dist/server/dynamic-config.js +447 -38
- package/dist/server/fulfillment.d.ts +1 -1
- package/dist/server/fulfillment.d.ts.map +1 -1
- package/dist/server/home.d.ts +31 -4
- package/dist/server/home.d.ts.map +1 -1
- package/dist/server/home.js +89 -10
- package/dist/server/homepage-section-defaults.d.ts +155 -0
- package/dist/server/homepage-section-defaults.d.ts.map +1 -0
- package/dist/server/homepage-section-defaults.js +149 -0
- package/dist/server/swaps.d.ts +6 -5
- package/dist/server/swaps.d.ts.map +1 -1
- package/dist/server/swaps.js +1 -1
- package/dist/server/wishlist.js +1 -1
- package/package.json +2 -2
- package/src/server/customer.ts +6 -1
- package/src/server/dynamic-config.ts +545 -45
- package/src/server/home.ts +155 -12
- package/src/server/homepage-section-defaults.ts +160 -0
- package/src/server/swaps.ts +8 -6
- package/src/server/wishlist.ts +1 -1
package/src/server/home.ts
CHANGED
|
@@ -1,24 +1,89 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
|
|
3
|
+
import { HttpTypes } from "@medusajs/types";
|
|
3
4
|
import { listCollections } from "./collections";
|
|
4
5
|
import { listCategories } from "./categories";
|
|
5
|
-
import { listProductsWithSort, getProductsByTag } from "./products";
|
|
6
|
+
import { listProductsWithSort, getProductsByTag, listProducts } from "./products";
|
|
6
7
|
import { getRegion } from "./regions";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getTestimonialsFromConfig,
|
|
10
|
+
getVideoStoriesFromConfig,
|
|
11
|
+
getAnnouncementMessagesFromConfig,
|
|
12
|
+
getAboutBrandFromConfig,
|
|
13
|
+
getBrandPillarsFromConfig,
|
|
14
|
+
getBlogPostsFromConfig,
|
|
15
|
+
getPromoCountdownFromConfig,
|
|
16
|
+
getHomeSectionsCopyFromConfig,
|
|
17
|
+
getTrustFeaturesFromConfig,
|
|
18
|
+
type AboutBrandConfig,
|
|
19
|
+
type BrandPillarConfig,
|
|
20
|
+
type BlogPostConfig,
|
|
21
|
+
type HomeSectionsCopy,
|
|
22
|
+
type TrustFeaturesConfig,
|
|
23
|
+
} from "./dynamic-config";
|
|
8
24
|
import { fetchRatings } from "medusa-reviews-logic/server";
|
|
9
25
|
|
|
26
|
+
async function productsForCategoryIds(
|
|
27
|
+
countryCode: string,
|
|
28
|
+
categoryIds: string[],
|
|
29
|
+
limit: number
|
|
30
|
+
): Promise<HttpTypes.StoreProduct[]> {
|
|
31
|
+
if (!categoryIds.length) return [];
|
|
32
|
+
try {
|
|
33
|
+
const { response } = await listProducts({
|
|
34
|
+
countryCode,
|
|
35
|
+
queryParams: {
|
|
36
|
+
category_id: categoryIds,
|
|
37
|
+
limit,
|
|
38
|
+
} as HttpTypes.FindParams & HttpTypes.StoreProductListParams,
|
|
39
|
+
});
|
|
40
|
+
return response.products ?? [];
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function categoryIdsMatching(
|
|
47
|
+
categories: HttpTypes.StoreProductCategory[],
|
|
48
|
+
pattern: RegExp
|
|
49
|
+
): string[] {
|
|
50
|
+
return categories
|
|
51
|
+
.filter((c) => pattern.test((c.name || c.handle || "").toLowerCase()))
|
|
52
|
+
.map((c) => c.id)
|
|
53
|
+
.filter(Boolean) as string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
10
56
|
export type HomePageData = {
|
|
11
57
|
region: Awaited<ReturnType<typeof getRegion>>;
|
|
12
58
|
collections: Array<Record<string, unknown>>;
|
|
13
|
-
categories:
|
|
14
|
-
newArrivals:
|
|
15
|
-
bestsellers:
|
|
59
|
+
categories: HttpTypes.StoreProductCategory[];
|
|
60
|
+
newArrivals: HttpTypes.StoreProduct[];
|
|
61
|
+
bestsellers: HttpTypes.StoreProduct[];
|
|
16
62
|
testimonials: unknown | null;
|
|
17
63
|
ratings: unknown[];
|
|
64
|
+
videoStories: { title: string; videoUrls: string[] } | null;
|
|
65
|
+
announcementMessages: string[];
|
|
66
|
+
promoCountdown: {
|
|
67
|
+
code: string | null;
|
|
68
|
+
message: string | null;
|
|
69
|
+
endAt: string | null;
|
|
70
|
+
active: boolean;
|
|
71
|
+
};
|
|
72
|
+
luxeFavourites: HttpTypes.StoreProduct[];
|
|
73
|
+
celebrityProduct: HttpTypes.StoreProduct | null;
|
|
74
|
+
baptismGirl: HttpTypes.StoreProduct[];
|
|
75
|
+
baptismBoy: HttpTypes.StoreProduct[];
|
|
76
|
+
baptismPicks: HttpTypes.StoreProduct[];
|
|
77
|
+
themeProducts: HttpTypes.StoreProduct[];
|
|
78
|
+
aboutBrand: AboutBrandConfig;
|
|
79
|
+
brandPillars: { sectionTitle: string; pillars: BrandPillarConfig[] };
|
|
80
|
+
blogPosts: BlogPostConfig[];
|
|
81
|
+
sectionsCopy: HomeSectionsCopy;
|
|
82
|
+
trustFeatures: TrustFeaturesConfig;
|
|
18
83
|
};
|
|
19
84
|
|
|
20
85
|
/**
|
|
21
|
-
* Parallel home page data loader
|
|
86
|
+
* Parallel home page data loader for full storefront homepage sections.
|
|
22
87
|
*/
|
|
23
88
|
export async function loadHomePageData(countryCode: string): Promise<HomePageData | null> {
|
|
24
89
|
const region = await getRegion(countryCode);
|
|
@@ -31,14 +96,24 @@ export async function loadHomePageData(countryCode: string): Promise<HomePageDat
|
|
|
31
96
|
bestsellersResult,
|
|
32
97
|
testimonialsData,
|
|
33
98
|
allRatings,
|
|
99
|
+
videoStoriesData,
|
|
100
|
+
announcementMessages,
|
|
101
|
+
promoCountdown,
|
|
102
|
+
aboutBrand,
|
|
103
|
+
brandPillars,
|
|
104
|
+
blogPosts,
|
|
105
|
+
themeByTag,
|
|
106
|
+
baptismByTag,
|
|
107
|
+
sectionsCopy,
|
|
108
|
+
trustFeatures,
|
|
34
109
|
] = await Promise.all([
|
|
35
110
|
listCollections({ fields: "id, handle, title, metadata" }).catch(() => ({
|
|
36
111
|
collections: [],
|
|
37
112
|
})),
|
|
38
|
-
listCategories({ limit:
|
|
113
|
+
listCategories({ limit: 100 }).catch(() => []),
|
|
39
114
|
listProductsWithSort({
|
|
40
115
|
page: 1,
|
|
41
|
-
queryParams: { limit:
|
|
116
|
+
queryParams: { limit: 12 },
|
|
42
117
|
sortBy: "created_at",
|
|
43
118
|
countryCode,
|
|
44
119
|
}).catch(() => ({ response: { products: [] } })),
|
|
@@ -47,22 +122,90 @@ export async function loadHomePageData(countryCode: string): Promise<HomePageDat
|
|
|
47
122
|
limit: 10,
|
|
48
123
|
countryCode,
|
|
49
124
|
}).catch(() => []),
|
|
50
|
-
getTestimonialsFromConfig().catch(() =>
|
|
125
|
+
getTestimonialsFromConfig().catch(() => ({
|
|
126
|
+
title: "",
|
|
127
|
+
testimonials: [],
|
|
128
|
+
})),
|
|
51
129
|
fetchRatings(),
|
|
130
|
+
getVideoStoriesFromConfig().catch(() => ({
|
|
131
|
+
title: "",
|
|
132
|
+
videoUrls: [],
|
|
133
|
+
})),
|
|
134
|
+
getAnnouncementMessagesFromConfig(),
|
|
135
|
+
getPromoCountdownFromConfig(),
|
|
136
|
+
getAboutBrandFromConfig(),
|
|
137
|
+
getBrandPillarsFromConfig(),
|
|
138
|
+
getBlogPostsFromConfig(),
|
|
139
|
+
getProductsByTag({ tagValue: "theme", limit: 8, countryCode }).catch(() => []),
|
|
140
|
+
getProductsByTag({ tagValue: "baptism", limit: 12, countryCode }).catch(() => []),
|
|
141
|
+
getHomeSectionsCopyFromConfig(),
|
|
142
|
+
getTrustFeaturesFromConfig(),
|
|
52
143
|
]);
|
|
53
144
|
|
|
145
|
+
const categories = (categoriesResult ?? []) as HttpTypes.StoreProductCategory[];
|
|
54
146
|
const collections =
|
|
55
147
|
(collectionsResult as { collections?: unknown[] }).collections ?? [];
|
|
56
148
|
const newArrivals =
|
|
57
|
-
(newArrivalsResult as { response?: { products?:
|
|
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;
|
|
172
|
+
}
|
|
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
|
+
);
|
|
58
187
|
|
|
59
188
|
return {
|
|
60
189
|
region,
|
|
61
190
|
collections: collections as Array<Record<string, unknown>>,
|
|
62
|
-
categories
|
|
191
|
+
categories,
|
|
63
192
|
newArrivals,
|
|
64
|
-
bestsellers
|
|
193
|
+
bestsellers,
|
|
65
194
|
testimonials: testimonialsData,
|
|
66
195
|
ratings: allRatings ?? [],
|
|
196
|
+
videoStories: videoStoriesData,
|
|
197
|
+
announcementMessages,
|
|
198
|
+
promoCountdown,
|
|
199
|
+
luxeFavourites: bestsellers.slice(0, 4),
|
|
200
|
+
celebrityProduct: bestsellers[0] ?? newArrivals[0] ?? null,
|
|
201
|
+
baptismGirl: resolvedBaptismGirl,
|
|
202
|
+
baptismBoy: resolvedBaptismBoy,
|
|
203
|
+
baptismPicks: (baptismByTag.length ? baptismByTag : baptismFallback).slice(0, 5),
|
|
204
|
+
themeProducts,
|
|
205
|
+
aboutBrand,
|
|
206
|
+
brandPillars,
|
|
207
|
+
blogPosts,
|
|
208
|
+
sectionsCopy,
|
|
209
|
+
trustFeatures,
|
|
67
210
|
};
|
|
68
211
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `homepage-config.sections` blocks.
|
|
3
|
+
* Dynamic config overrides any field; unset fields keep these defaults.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_HOMEPAGE_SECTIONS = {
|
|
6
|
+
promoAnnouncements: {
|
|
7
|
+
messages: [
|
|
8
|
+
"Free shipping on orders over a threshold",
|
|
9
|
+
"Worldwide shipping available",
|
|
10
|
+
"Selected products available with customisation",
|
|
11
|
+
"Need help choosing? Book a video call consultation",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
hero: {},
|
|
15
|
+
categoryPills: {},
|
|
16
|
+
newArrivals: {
|
|
17
|
+
title: "New Arrivals",
|
|
18
|
+
subtitle: "Fresh styles just landed",
|
|
19
|
+
},
|
|
20
|
+
promoCountdown: {
|
|
21
|
+
text: "Limited-time offer — shop your favourites before they're gone!",
|
|
22
|
+
code: "SAVE20",
|
|
23
|
+
active: false,
|
|
24
|
+
},
|
|
25
|
+
celebrityTrust: {
|
|
26
|
+
title: "Trusted by Celebrities",
|
|
27
|
+
text: "When the stars dress their little ones in our collections.",
|
|
28
|
+
},
|
|
29
|
+
luxeFavourites: {
|
|
30
|
+
title: "Luxe Favourites",
|
|
31
|
+
},
|
|
32
|
+
shopByCategory: {
|
|
33
|
+
title: "Shop By Categories",
|
|
34
|
+
description: "Browse by style and occasion",
|
|
35
|
+
},
|
|
36
|
+
baptism: {
|
|
37
|
+
title: "Baptism",
|
|
38
|
+
},
|
|
39
|
+
baptismPicks: {
|
|
40
|
+
title: "Baptism Picks",
|
|
41
|
+
},
|
|
42
|
+
themeDresses: {
|
|
43
|
+
title: "Theme",
|
|
44
|
+
description:
|
|
45
|
+
"Dress her in a dreamy theme outfit for her birthday celebration",
|
|
46
|
+
},
|
|
47
|
+
brandMarquee: {
|
|
48
|
+
text: "est. 2012",
|
|
49
|
+
},
|
|
50
|
+
aboutBrand: {
|
|
51
|
+
eyebrow: "Who We Are",
|
|
52
|
+
title: "Discover what makes us different",
|
|
53
|
+
description:
|
|
54
|
+
"We offer stylish, comfortable, and high-quality clothing for children, crafted for everyday wear and special occasions.",
|
|
55
|
+
readMoreHref: "/about",
|
|
56
|
+
stats: [
|
|
57
|
+
{ label: "Visit us in store", value: "Find a location" },
|
|
58
|
+
{ label: "Happy customers", value: "150k+" },
|
|
59
|
+
{ label: "Products", value: "3000+" },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
testimonials: {
|
|
63
|
+
title: "What Clients Talk About Us",
|
|
64
|
+
description: "The Trust We've Earned",
|
|
65
|
+
},
|
|
66
|
+
brandPillars: {
|
|
67
|
+
title: "This approach resulted in the beautiful structure",
|
|
68
|
+
pillars: [
|
|
69
|
+
{
|
|
70
|
+
title: "Your favorite kids destination",
|
|
71
|
+
description:
|
|
72
|
+
"Trendy, high-quality outfits for little ones—pieces designed for comfort, celebration, and the memories you make together.",
|
|
73
|
+
image: "/features/store.jpg",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: "Video call consultation",
|
|
77
|
+
description:
|
|
78
|
+
"Shop from home with our video call service. Our team shows styles in real-time and helps you pick the perfect outfits.",
|
|
79
|
+
image: "/features/video.jpg",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "Design Your Dream Outfit",
|
|
83
|
+
description:
|
|
84
|
+
"We customize colors, themes, and handwork to create a perfectly fitted outfit for your little one's special moments.",
|
|
85
|
+
image: "/features/design.jpg",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: "Luxury Fabrics, Made for Kids",
|
|
89
|
+
description:
|
|
90
|
+
"We use premium, breathable fabrics so each outfit feels luxurious and comfortable all day.",
|
|
91
|
+
image: "/features/fabric.jpg",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
features: {
|
|
96
|
+
title: "Why shop with us",
|
|
97
|
+
description: "Secure checkout, fast delivery, and easy exchanges",
|
|
98
|
+
features: [
|
|
99
|
+
{ name: "100% Secure", icon: "/secure.svg" },
|
|
100
|
+
{ name: "Easy Exchanges", icon: "/exchnage.svg" },
|
|
101
|
+
{ name: "Fast Delivery", icon: "/fast.svg" },
|
|
102
|
+
{ name: "Cash On Delivery", icon: "/cash.svg" },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
whyChooseUs: {
|
|
106
|
+
title: "Why Choose Us?",
|
|
107
|
+
features: [
|
|
108
|
+
{ "feature-name": "Premium Quality", "feature-icon": "/icons/quality.svg" },
|
|
109
|
+
{ "feature-name": "Easy Returns", "feature-icon": "/icons/returns.svg" },
|
|
110
|
+
{ "feature-name": "Fast Delivery", "feature-icon": "/icons/delivery.svg" },
|
|
111
|
+
{ "feature-name": "Secure Checkout", "feature-icon": "/icons/secure.svg" },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
lovedByMoms: {
|
|
115
|
+
title: "Loved by Moms",
|
|
116
|
+
description: "Top-rated styles loved by parents and kids alike.",
|
|
117
|
+
},
|
|
118
|
+
shopByAge: {
|
|
119
|
+
title: "Shop By Age",
|
|
120
|
+
},
|
|
121
|
+
videoStories: {
|
|
122
|
+
title: "Watch Stories & Reviews",
|
|
123
|
+
"video-urls": [],
|
|
124
|
+
},
|
|
125
|
+
blogPosts: {
|
|
126
|
+
title: "Blog posts",
|
|
127
|
+
posts: [
|
|
128
|
+
{
|
|
129
|
+
title: "Which Theme Dress Matches Her Vibe?",
|
|
130
|
+
href: "/blog/theme-dress-vibe",
|
|
131
|
+
category: "Theme Dress",
|
|
132
|
+
date: "May 24, 2026",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
title: "How to Choose a Birthday Frock That Matches Your Party Theme",
|
|
136
|
+
href: "/blog/birthday-frock-party-theme",
|
|
137
|
+
category: "Birthday Dress",
|
|
138
|
+
date: "May 14, 2026",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Beyond Pink: Trending Colors in Kid's Party Wear",
|
|
142
|
+
href: "/blog/trending-party-wear-colors",
|
|
143
|
+
category: "Girls Party Dresses",
|
|
144
|
+
date: "Apr 30, 2026",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
title: "Wearable Wonders: 3D Hand-Embroidered Theme Dresses",
|
|
148
|
+
href: "/blog/3d-hand-embroidered-dresses",
|
|
149
|
+
category: "Birthday dress for Baby Girl",
|
|
150
|
+
date: "Apr 25, 2026",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
} as const satisfies Record<string, Record<string, unknown>>
|
|
155
|
+
|
|
156
|
+
export type DefaultHomeSectionId = keyof typeof DEFAULT_HOMEPAGE_SECTIONS
|
|
157
|
+
|
|
158
|
+
export const HOME_SECTION_COPY_ID_LIST = Object.keys(
|
|
159
|
+
DEFAULT_HOMEPAGE_SECTIONS
|
|
160
|
+
) as DefaultHomeSectionId[]
|
package/src/server/swaps.ts
CHANGED
|
@@ -10,18 +10,20 @@ import { cookies } from "next/headers"
|
|
|
10
10
|
import { revalidateTag } from "next/cache"
|
|
11
11
|
import { getStoreClientOptions, getStoreClientOptionsWithToken } from "../util/store-client"
|
|
12
12
|
|
|
13
|
+
export type SwapActionState = {
|
|
14
|
+
success: boolean
|
|
15
|
+
error: string | null
|
|
16
|
+
swap: Record<string, unknown> | null
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* Create an exchange (swap) request.
|
|
15
21
|
* Supports both authenticated customers and guest users.
|
|
16
22
|
*/
|
|
17
23
|
export const createSwapRequest = async (
|
|
18
|
-
|
|
24
|
+
_prevState: SwapActionState,
|
|
19
25
|
formData: FormData
|
|
20
|
-
): Promise<{
|
|
21
|
-
success: boolean
|
|
22
|
-
error: string | null
|
|
23
|
-
swap: Record<string, unknown> | null
|
|
24
|
-
}> => {
|
|
26
|
+
): Promise<SwapActionState> => {
|
|
25
27
|
const orderId = formData.get("order_id") as string
|
|
26
28
|
const returnItemsJson = formData.get("return_items") as string
|
|
27
29
|
const newItemsJson = formData.get("new_items") as string
|
package/src/server/wishlist.ts
CHANGED
|
@@ -43,7 +43,7 @@ export async function getWishlist(includeDetails = true, countryCode?: string) {
|
|
|
43
43
|
"*thumbnail,*images,*variants,*variants.options,*options,*options.values,*variants.calculated_price,*variants.inventory_quantity,*variants.manage_inventory,*variants.allow_backorder",
|
|
44
44
|
...(regionId ? { region_id: regionId } : {}),
|
|
45
45
|
},
|
|
46
|
-
{
|
|
46
|
+
{}
|
|
47
47
|
);
|
|
48
48
|
|
|
49
49
|
const fullProducts = productsResponse.products as HttpTypes.StoreProduct[];
|