medusa-storefront-data 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/config.d.ts +3 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +31 -0
  4. package/dist/cookies.d.ts +23 -0
  5. package/dist/cookies.d.ts.map +1 -0
  6. package/dist/cookies.js +140 -0
  7. package/dist/server/cart.d.ts +92 -0
  8. package/dist/server/cart.d.ts.map +1 -0
  9. package/dist/server/cart.js +827 -0
  10. package/dist/server/categories.d.ts +3 -0
  11. package/dist/server/categories.d.ts.map +1 -0
  12. package/dist/server/categories.js +71 -0
  13. package/dist/server/collections.d.ts +8 -0
  14. package/dist/server/collections.d.ts.map +1 -0
  15. package/dist/server/collections.js +84 -0
  16. package/dist/server/customer-registration.d.ts +142 -0
  17. package/dist/server/customer-registration.d.ts.map +1 -0
  18. package/dist/server/customer-registration.js +295 -0
  19. package/dist/server/customer.d.ts +48 -0
  20. package/dist/server/customer.d.ts.map +1 -0
  21. package/dist/server/customer.js +462 -0
  22. package/dist/server/dynamic-config.d.ts +125 -0
  23. package/dist/server/dynamic-config.d.ts.map +1 -0
  24. package/dist/server/dynamic-config.js +263 -0
  25. package/dist/server/fulfillment.d.ts +4 -0
  26. package/dist/server/fulfillment.d.ts.map +1 -0
  27. package/dist/server/fulfillment.js +72 -0
  28. package/dist/server/guest.d.ts +109 -0
  29. package/dist/server/guest.d.ts.map +1 -0
  30. package/dist/server/guest.js +304 -0
  31. package/dist/server/index.d.ts +21 -0
  32. package/dist/server/index.d.ts.map +1 -0
  33. package/dist/server/index.js +20 -0
  34. package/dist/server/locale-actions.d.ts +14 -0
  35. package/dist/server/locale-actions.d.ts.map +1 -0
  36. package/dist/server/locale-actions.js +63 -0
  37. package/dist/server/locales.d.ts +10 -0
  38. package/dist/server/locales.d.ts.map +1 -0
  39. package/dist/server/locales.js +20 -0
  40. package/dist/server/notifications.d.ts +2 -0
  41. package/dist/server/notifications.d.ts.map +1 -0
  42. package/dist/server/notifications.js +20 -0
  43. package/dist/server/onboarding.d.ts +2 -0
  44. package/dist/server/onboarding.d.ts.map +1 -0
  45. package/dist/server/onboarding.js +8 -0
  46. package/dist/server/orders.d.ts +69 -0
  47. package/dist/server/orders.d.ts.map +1 -0
  48. package/dist/server/orders.js +371 -0
  49. package/dist/server/payment-details.d.ts +5 -0
  50. package/dist/server/payment-details.d.ts.map +1 -0
  51. package/dist/server/payment-details.js +53 -0
  52. package/dist/server/payment.d.ts +2 -0
  53. package/dist/server/payment.d.ts.map +1 -0
  54. package/dist/server/payment.js +25 -0
  55. package/dist/server/products.d.ts +58 -0
  56. package/dist/server/products.d.ts.map +1 -0
  57. package/dist/server/products.js +285 -0
  58. package/dist/server/regions.d.ts +5 -0
  59. package/dist/server/regions.d.ts.map +1 -0
  60. package/dist/server/regions.js +54 -0
  61. package/dist/server/returns.d.ts +29 -0
  62. package/dist/server/returns.d.ts.map +1 -0
  63. package/dist/server/returns.js +236 -0
  64. package/dist/server/swaps.d.ts +14 -0
  65. package/dist/server/swaps.d.ts.map +1 -0
  66. package/dist/server/swaps.js +123 -0
  67. package/dist/server/variants.d.ts +3 -0
  68. package/dist/server/variants.d.ts.map +1 -0
  69. package/dist/server/variants.js +26 -0
  70. package/dist/util/get-locale-header.d.ts +4 -0
  71. package/dist/util/get-locale-header.d.ts.map +1 -0
  72. package/dist/util/get-locale-header.js +7 -0
  73. package/dist/util/medusa-error.d.ts +2 -0
  74. package/dist/util/medusa-error.d.ts.map +1 -0
  75. package/dist/util/medusa-error.js +18 -0
  76. package/package.json +152 -0
  77. package/src/config.ts +39 -0
  78. package/src/cookies.ts +171 -0
  79. package/src/middleware.ts +2 -0
  80. package/src/server/cart.ts +1054 -0
  81. package/src/server/categories.ts +94 -0
  82. package/src/server/collections.ts +113 -0
  83. package/src/server/customer-registration.ts +349 -0
  84. package/src/server/customer.ts +581 -0
  85. package/src/server/dynamic-config.ts +403 -0
  86. package/src/server/fulfillment.ts +97 -0
  87. package/src/server/guest.ts +333 -0
  88. package/src/server/index.ts +21 -0
  89. package/src/server/locale-actions.ts +74 -0
  90. package/src/server/locales.ts +28 -0
  91. package/src/server/notifications.ts +22 -0
  92. package/src/server/onboarding.ts +9 -0
  93. package/src/server/orders.ts +467 -0
  94. package/src/server/payment-details.ts +69 -0
  95. package/src/server/payment.ts +35 -0
  96. package/src/server/products.ts +378 -0
  97. package/src/server/regions.ts +66 -0
  98. package/src/server/returns.ts +294 -0
  99. package/src/server/swaps.ts +150 -0
  100. package/src/server/variants.ts +38 -0
  101. package/src/server/wishlist.ts +64 -0
  102. package/src/services/middleware.ts +54 -0
  103. package/src/util/get-locale-header.ts +8 -0
  104. package/src/util/medusa-error.ts +19 -0
  105. package/src/util/sort-products.ts +47 -0
@@ -0,0 +1,378 @@
1
+ "use server"
2
+
3
+ import { sdk } from "../config"
4
+ import { sortProducts, type SortOptions } from "../util/sort-products"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { getAuthHeaders, getCacheOptions } from "../cookies"
7
+ import { getRegion, retrieveRegion } from "./regions"
8
+ import Color from "color"
9
+
10
+ export const listProducts = async ({
11
+ pageParam = 1,
12
+ queryParams,
13
+ countryCode,
14
+ regionId,
15
+ }: {
16
+ pageParam?: number
17
+ queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductListParams
18
+ countryCode?: string
19
+ regionId?: string
20
+ }): Promise<{
21
+ response: { products: HttpTypes.StoreProduct[]; count: number }
22
+ nextPage: number | null
23
+ queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductListParams
24
+ }> => {
25
+
26
+
27
+ if (!countryCode && !regionId) {
28
+
29
+ throw new Error("Country code or region ID is required")
30
+ }
31
+
32
+ const limit = queryParams?.limit || 12
33
+ const _pageParam = Math.max(pageParam, 1)
34
+ const offset = _pageParam === 1 ? 0 : (_pageParam - 1) * limit
35
+
36
+ let region: HttpTypes.StoreRegion | undefined | null
37
+
38
+ if (countryCode) {
39
+
40
+ region = await getRegion(countryCode)
41
+ } else {
42
+
43
+ region = await retrieveRegion(regionId!)
44
+ }
45
+
46
+ if (!region) {
47
+ return {
48
+ response: { products: [], count: 0 },
49
+ nextPage: null,
50
+ }
51
+ }
52
+
53
+
54
+
55
+ const headers = {
56
+ ...(await getAuthHeaders()),
57
+ }
58
+
59
+ const next = {
60
+ ...(await getCacheOptions("products")),
61
+ }
62
+
63
+ // 1. Standard Medusa query parameters
64
+ const standardQuery: any = {
65
+ limit,
66
+ offset,
67
+ region_id: region?.id,
68
+ fields:
69
+ "*variants.calculated_price,+variants.inventory_quantity,*variants.images,+variants.metadata,+variants.options,+metadata,+tags,+average_rating,+total_rating_count,+total_rating_sum,",
70
+ }
71
+
72
+ // 2. Identify advanced filters that require the product-helper API
73
+ const advancedFilterKeys = [
74
+ "metadata", "min_price", "max_price", "q", "collection_id", "category_id",
75
+ "tags", "option_value", "gender", "color", "material", "style", "brand",
76
+ "type", "product_type", "order"
77
+ ]
78
+
79
+ const hasAdvancedFilters = !!(queryParams && Object.keys(queryParams).some(key =>
80
+ advancedFilterKeys.includes(key) && (queryParams as any)[key] !== undefined
81
+ ))
82
+
83
+ const endpoint = hasAdvancedFilters ? "/store/product-helper/products" : "/store/products";
84
+
85
+ let finalQuery: any = { ...standardQuery }
86
+
87
+ if (hasAdvancedFilters) {
88
+
89
+
90
+ // We use URLSearchParams to ensure correct formatting and handle repeated keys if needed
91
+ const params = new URLSearchParams()
92
+
93
+ // 1. Add standard params
94
+ params.append("limit", limit.toString())
95
+ params.append("offset", offset.toString())
96
+ if (region?.id) params.append("region_id", region.id)
97
+
98
+ // Use '*' for helper endpoint to let it resolve necessary relations
99
+ params.append("fields", "*")
100
+
101
+ // 2. Map queryParams
102
+ for (const [key, value] of Object.entries(queryParams || {})) {
103
+ if (["metadata", "min_price", "max_price", "limit", "offset", "region_id", "fields"].includes(key)) continue
104
+
105
+ const values = Array.isArray(value) ? value : [value]
106
+ const lowerKey = key.toLowerCase()
107
+
108
+ if (lowerKey === "color") {
109
+ // Map 'color' filter to 'option_value' which backend understands for variants
110
+ values.forEach(v => params.append("option_value[]", String(v)))
111
+ } else if (["material", "gender", "product_type", "type", "style", "brand"].includes(lowerKey)) {
112
+ // Map these to metadata as they are defined as metadata in your config
113
+ const metaKey = lowerKey === "type" ? "product_type" : lowerKey
114
+ values.forEach(v => params.append(`metadata[${metaKey}]`, String(v)))
115
+ } else {
116
+ // Standard mapping for others (tags, collection_id, etc.)
117
+ if (Array.isArray(value)) {
118
+ value.forEach(v => params.append(`${key}[]`, String(v)))
119
+ } else if (value !== undefined && value !== null) {
120
+ params.append(key, String(value))
121
+ }
122
+ }
123
+ }
124
+
125
+ // 3. Map price filters (min_price -> price_min, max_price -> price_max)
126
+ if ((queryParams as any).min_price) params.append("price_min", String((queryParams as any).min_price))
127
+ if ((queryParams as any).max_price) params.append("price_max", String((queryParams as any).max_price))
128
+
129
+ // 4. Map metadata filters to metadata[key] format
130
+ if ((queryParams as any).metadata) {
131
+ for (const [key, value] of Object.entries((queryParams as any).metadata)) {
132
+ const values = Array.isArray(value) ? value : [value]
133
+
134
+ values.forEach(v => {
135
+ if (key.toLowerCase() === "color") {
136
+ // Map color in metadata to option_value[]
137
+ params.append("option_value[]", String(v))
138
+ } else {
139
+ // Standard metadata format: metadata[key]=value
140
+ params.append(`metadata[${key}]`, String(v))
141
+ }
142
+ })
143
+ }
144
+ }
145
+
146
+ finalQuery = params
147
+
148
+ } else {
149
+ // Standard endpoint - only merge known safe Medusa keys from queryParams
150
+ // to avoid "Unrecognized fields" errors
151
+ const safeKeys = ["order", "id", "handle", "status", "created_at", "updated_at", "type_id"]
152
+ for (const key of safeKeys) {
153
+ if (queryParams && (queryParams as any)[key]) {
154
+ finalQuery[key] = (queryParams as any)[key]
155
+ }
156
+ }
157
+ }
158
+
159
+ // Use the appended query string to avoid SDK's default serialization issues with brackets
160
+ const requestUrl = hasAdvancedFilters ? `${endpoint}?${finalQuery.toString()}` : endpoint;
161
+
162
+ return sdk.client
163
+ .fetch<{ products: HttpTypes.StoreProduct[]; count: number }>(
164
+ requestUrl,
165
+ {
166
+ method: "GET",
167
+ query: hasAdvancedFilters ? undefined : finalQuery,
168
+ headers,
169
+ next: {
170
+ ...next,
171
+ revalidate: 0, // Disable Next.js cache for now to show logs
172
+ },
173
+ cache: "no-store", // Ensure it hits backend every time for testing
174
+ }
175
+ )
176
+ .then(({ products, count }) => {
177
+
178
+
179
+ const nextPage = count > offset + limit ? _pageParam + 1 : null
180
+
181
+ if (products && products.length > 0) {
182
+
183
+ }
184
+
185
+ return {
186
+ response: {
187
+ products: products || [],
188
+ count: count || 0,
189
+ },
190
+ nextPage: nextPage,
191
+ queryParams,
192
+ }
193
+ })
194
+ .catch((error) => {
195
+ throw error
196
+ })
197
+ }
198
+
199
+ /**
200
+ * This will fetch 100 products to the Next.js cache and sort them based on the sortBy parameter.
201
+ * It will then return the paginated products based on the page and limit parameters.
202
+ */
203
+ export const listProductsWithSort = async ({
204
+ page = 1,
205
+ queryParams,
206
+ sortBy = "created_at_desc",
207
+ countryCode,
208
+ }: {
209
+ page?: number
210
+ queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
211
+ sortBy?: SortOptions
212
+ countryCode: string
213
+ }): Promise<{
214
+ response: { products: HttpTypes.StoreProduct[]; count: number }
215
+ nextPage: number | null
216
+ queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
217
+ }> => {
218
+ const limit = queryParams?.limit || 12
219
+
220
+ // Directly fetch from backend with correct pagination
221
+ // This ensures the backend terminal logs the specific request
222
+ const {
223
+ response: { products, count },
224
+ } = await listProducts({
225
+ pageParam: page,
226
+ queryParams: {
227
+ ...queryParams,
228
+ limit,
229
+ },
230
+ countryCode,
231
+ })
232
+
233
+ // Sorting is now handled natively by the backend API via the 'order' query param.
234
+ const finalProducts = products
235
+
236
+ const nextPage = count > page * limit ? page + 1 : null
237
+
238
+ return {
239
+ response: {
240
+ products: finalProducts,
241
+ count,
242
+ },
243
+ nextPage,
244
+ queryParams,
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Fetch products that have a specific tag value
250
+ */
251
+ export async function getProductsByTag({
252
+ tagValue,
253
+ limit = 12,
254
+ countryCode,
255
+ }: {
256
+ tagValue: string
257
+ limit?: number
258
+ countryCode: string
259
+ }) {
260
+ try {
261
+ const result = await listProducts({
262
+ queryParams: { tags: [tagValue], limit },
263
+ countryCode,
264
+ })
265
+
266
+ return result.response.products
267
+ } catch (error) {
268
+ console.error("Error fetching products by tag:", error)
269
+ return []
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Fetch dynamic filter options from the backend API
275
+ */
276
+ export const getDynamicFilters = async (countryCode: string) => {
277
+ const backendUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
278
+ const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
279
+
280
+ try {
281
+ if (!publishableKey) return { genders: [], productTypes: [], materials: [], colors: [] }
282
+
283
+ const response = await fetch(`${backendUrl}/store/product-helper/filters`, {
284
+ method: "GET",
285
+ headers: {
286
+ "Content-Type": "application/json",
287
+ "x-publishable-api-key": publishableKey,
288
+ },
289
+ cache: "no-store",
290
+ })
291
+
292
+ if (!response.ok) {
293
+ return { genders: [], productTypes: [], materials: [], colors: [] }
294
+ }
295
+
296
+ const data = await response.json()
297
+
298
+ const formatLabel = (str: string) => {
299
+ return str
300
+ .split(/[_-]/)
301
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
302
+ .join(' ')
303
+ }
304
+
305
+ const metadata = data.metadata || {}
306
+ const variantOptions = data.variant_options || []
307
+
308
+ // Gather all unique colors from all possible sources
309
+ const colorSet = new Set<string>()
310
+
311
+ // 1. From variant options (check all options with title 'color')
312
+ variantOptions.forEach((o: any) => {
313
+ if (o.option_title.toLowerCase() === 'color' || o.option_title.toLowerCase() === 'colour') {
314
+ o.values?.forEach((v: string) => {
315
+ if (v) colorSet.add(v.trim().toLowerCase())
316
+ })
317
+ }
318
+ })
319
+
320
+ // 2. From metadata colors (handle newline/comma separated values)
321
+ if (metadata.color) {
322
+ metadata.color.forEach((c: string) => {
323
+ if (c) {
324
+ const parts = c.split(/[,/\n\r]+/).map(s => s.trim()).filter(Boolean)
325
+ parts.forEach(p => colorSet.add(p.toLowerCase()))
326
+ }
327
+ })
328
+ }
329
+
330
+ return {
331
+ genders: (metadata.gender || []).sort().map((g: string) => ({
332
+ value: g,
333
+ label: formatLabel(g)
334
+ })),
335
+ productTypes: (data.product_types || []).sort((a: any, b: any) => a.label.localeCompare(b.label)).map((t: any) => ({
336
+ value: t.label,
337
+ label: formatLabel(t.label)
338
+ })),
339
+ materials: (metadata.material || []).sort().map((m: string) => ({
340
+ value: m,
341
+ label: formatLabel(m)
342
+ })),
343
+ colors: Array.from(colorSet).sort().map((c: string) => {
344
+ let hex = ""
345
+ try {
346
+ // Try to resolve color hex code using the Color library
347
+ hex = Color(c.replace(/\s+/g, '').toLowerCase()).hex()
348
+ } catch (e) {
349
+ try {
350
+ hex = Color(c.toLowerCase()).hex()
351
+ } catch (e2) {
352
+ // No hex found
353
+ }
354
+ }
355
+ return {
356
+ value: c,
357
+ label: formatLabel(c),
358
+ hex
359
+ }
360
+ })
361
+ }
362
+ } catch (error) {
363
+ console.error("Error fetching dynamic filters:", error)
364
+ return { genders: [], productTypes: [], materials: [], colors: [] }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Fetch a single product by its handle
370
+ */
371
+ export async function getProductByHandle(handle: string) {
372
+ const result = await sdk.store.product.list(
373
+ { handle, fields: "*variants.calculated_price,+variants.inventory_quantity,+variants.manage_inventory,+variants.allow_backorder,*variants.options,*options,*options.values,*images,*thumbnail,+metadata" },
374
+ { next: { tags: ["products"] } }
375
+ )
376
+
377
+ return result.products[0]
378
+ }
@@ -0,0 +1,66 @@
1
+ "use server"
2
+
3
+ import { sdk } from "../config"
4
+ import medusaError from "../util/medusa-error"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { getCacheOptions } from "../cookies"
7
+
8
+ export const listRegions = async () => {
9
+ const next = {
10
+ ...(await getCacheOptions("regions")),
11
+ }
12
+
13
+ return sdk.client
14
+ .fetch<{ regions: HttpTypes.StoreRegion[] }>(`/store/regions`, {
15
+ method: "GET",
16
+ next,
17
+ cache: "force-cache",
18
+ })
19
+ .then(({ regions }) => regions)
20
+ .catch(medusaError)
21
+ }
22
+
23
+ export const retrieveRegion = async (id: string) => {
24
+ const next = {
25
+ ...(await getCacheOptions(["regions", id].join("-"))),
26
+ }
27
+
28
+ return sdk.client
29
+ .fetch<{ region: HttpTypes.StoreRegion }>(`/store/regions/${id}`, {
30
+ method: "GET",
31
+ next,
32
+ cache: "force-cache",
33
+ })
34
+ .then(({ region }) => region)
35
+ .catch(medusaError)
36
+ }
37
+
38
+ const regionMap = new Map<string, HttpTypes.StoreRegion>()
39
+
40
+ export const getRegion = async (countryCode: string) => {
41
+ try {
42
+ if (regionMap.has(countryCode)) {
43
+ return regionMap.get(countryCode)
44
+ }
45
+
46
+ const regions = await listRegions()
47
+
48
+ if (!regions) {
49
+ return null
50
+ }
51
+
52
+ regions.forEach((region) => {
53
+ region.countries?.forEach((c) => {
54
+ regionMap.set(c?.iso_2 ?? "", region)
55
+ })
56
+ })
57
+
58
+ const region = countryCode
59
+ ? regionMap.get(countryCode)
60
+ : regionMap.get("us")
61
+
62
+ return region
63
+ } catch (e: any) {
64
+ return null
65
+ }
66
+ }