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.
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +31 -0
- package/dist/cookies.d.ts +23 -0
- package/dist/cookies.d.ts.map +1 -0
- package/dist/cookies.js +140 -0
- package/dist/server/cart.d.ts +92 -0
- package/dist/server/cart.d.ts.map +1 -0
- package/dist/server/cart.js +827 -0
- package/dist/server/categories.d.ts +3 -0
- package/dist/server/categories.d.ts.map +1 -0
- package/dist/server/categories.js +71 -0
- package/dist/server/collections.d.ts +8 -0
- package/dist/server/collections.d.ts.map +1 -0
- package/dist/server/collections.js +84 -0
- package/dist/server/customer-registration.d.ts +142 -0
- package/dist/server/customer-registration.d.ts.map +1 -0
- package/dist/server/customer-registration.js +295 -0
- package/dist/server/customer.d.ts +48 -0
- package/dist/server/customer.d.ts.map +1 -0
- package/dist/server/customer.js +462 -0
- package/dist/server/dynamic-config.d.ts +125 -0
- package/dist/server/dynamic-config.d.ts.map +1 -0
- package/dist/server/dynamic-config.js +263 -0
- package/dist/server/fulfillment.d.ts +4 -0
- package/dist/server/fulfillment.d.ts.map +1 -0
- package/dist/server/fulfillment.js +72 -0
- package/dist/server/guest.d.ts +109 -0
- package/dist/server/guest.d.ts.map +1 -0
- package/dist/server/guest.js +304 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +20 -0
- package/dist/server/locale-actions.d.ts +14 -0
- package/dist/server/locale-actions.d.ts.map +1 -0
- package/dist/server/locale-actions.js +63 -0
- package/dist/server/locales.d.ts +10 -0
- package/dist/server/locales.d.ts.map +1 -0
- package/dist/server/locales.js +20 -0
- package/dist/server/notifications.d.ts +2 -0
- package/dist/server/notifications.d.ts.map +1 -0
- package/dist/server/notifications.js +20 -0
- package/dist/server/onboarding.d.ts +2 -0
- package/dist/server/onboarding.d.ts.map +1 -0
- package/dist/server/onboarding.js +8 -0
- package/dist/server/orders.d.ts +69 -0
- package/dist/server/orders.d.ts.map +1 -0
- package/dist/server/orders.js +371 -0
- package/dist/server/payment-details.d.ts +5 -0
- package/dist/server/payment-details.d.ts.map +1 -0
- package/dist/server/payment-details.js +53 -0
- package/dist/server/payment.d.ts +2 -0
- package/dist/server/payment.d.ts.map +1 -0
- package/dist/server/payment.js +25 -0
- package/dist/server/products.d.ts +58 -0
- package/dist/server/products.d.ts.map +1 -0
- package/dist/server/products.js +285 -0
- package/dist/server/regions.d.ts +5 -0
- package/dist/server/regions.d.ts.map +1 -0
- package/dist/server/regions.js +54 -0
- package/dist/server/returns.d.ts +29 -0
- package/dist/server/returns.d.ts.map +1 -0
- package/dist/server/returns.js +236 -0
- package/dist/server/swaps.d.ts +14 -0
- package/dist/server/swaps.d.ts.map +1 -0
- package/dist/server/swaps.js +123 -0
- package/dist/server/variants.d.ts +3 -0
- package/dist/server/variants.d.ts.map +1 -0
- package/dist/server/variants.js +26 -0
- package/dist/util/get-locale-header.d.ts +4 -0
- package/dist/util/get-locale-header.d.ts.map +1 -0
- package/dist/util/get-locale-header.js +7 -0
- package/dist/util/medusa-error.d.ts +2 -0
- package/dist/util/medusa-error.d.ts.map +1 -0
- package/dist/util/medusa-error.js +18 -0
- package/package.json +152 -0
- package/src/config.ts +39 -0
- package/src/cookies.ts +171 -0
- package/src/middleware.ts +2 -0
- package/src/server/cart.ts +1054 -0
- package/src/server/categories.ts +94 -0
- package/src/server/collections.ts +113 -0
- package/src/server/customer-registration.ts +349 -0
- package/src/server/customer.ts +581 -0
- package/src/server/dynamic-config.ts +403 -0
- package/src/server/fulfillment.ts +97 -0
- package/src/server/guest.ts +333 -0
- package/src/server/index.ts +21 -0
- package/src/server/locale-actions.ts +74 -0
- package/src/server/locales.ts +28 -0
- package/src/server/notifications.ts +22 -0
- package/src/server/onboarding.ts +9 -0
- package/src/server/orders.ts +467 -0
- package/src/server/payment-details.ts +69 -0
- package/src/server/payment.ts +35 -0
- package/src/server/products.ts +378 -0
- package/src/server/regions.ts +66 -0
- package/src/server/returns.ts +294 -0
- package/src/server/swaps.ts +150 -0
- package/src/server/variants.ts +38 -0
- package/src/server/wishlist.ts +64 -0
- package/src/services/middleware.ts +54 -0
- package/src/util/get-locale-header.ts +8 -0
- package/src/util/medusa-error.ts +19 -0
- package/src/util/sort-products.ts +47 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { sdk } from "../config"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
import { cookies } from "next/headers"
|
|
6
|
+
import { getAuthHeaders, getCacheOptions } from "../cookies"
|
|
7
|
+
import { revalidateTag } from "next/cache"
|
|
8
|
+
|
|
9
|
+
export const listReturnReasons = async () => {
|
|
10
|
+
const headers = {
|
|
11
|
+
...(await getAuthHeaders()),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const next = {
|
|
15
|
+
...(await getCacheOptions("return_reasons")),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return sdk.client
|
|
19
|
+
.fetch<HttpTypes.StoreReturnReasonListResponse>(`/store/return-reasons`, {
|
|
20
|
+
method: "GET",
|
|
21
|
+
headers,
|
|
22
|
+
next,
|
|
23
|
+
cache: "force-cache", // Reasons change rarely
|
|
24
|
+
})
|
|
25
|
+
.then(({ return_reasons }) => return_reasons)
|
|
26
|
+
.catch(() => [])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const listReturnShippingOptions = async (
|
|
30
|
+
cartId: string,
|
|
31
|
+
regionId?: string,
|
|
32
|
+
productIds?: string[]
|
|
33
|
+
): Promise<HttpTypes.StoreShippingOption[]> => {
|
|
34
|
+
const headers = {
|
|
35
|
+
...(await getAuthHeaders()),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const next = {
|
|
39
|
+
...(await getCacheOptions("fulfillment")),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Strategy 1: Try with cart_id (preferred as it has context)
|
|
43
|
+
if (cartId) {
|
|
44
|
+
try {
|
|
45
|
+
const { shipping_options } = await sdk.client.fetch<HttpTypes.StoreShippingOptionListResponse>(
|
|
46
|
+
`/store/shipping-options`,
|
|
47
|
+
{
|
|
48
|
+
method: "GET",
|
|
49
|
+
query: {
|
|
50
|
+
cart_id: cartId,
|
|
51
|
+
is_return: true,
|
|
52
|
+
},
|
|
53
|
+
headers,
|
|
54
|
+
next,
|
|
55
|
+
cache: "no-store",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (shipping_options.length > 0) {
|
|
60
|
+
return shipping_options as unknown as HttpTypes.StoreShippingOption[]
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// Fallback strategy
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Strategy 2: Fallback - Create ephemeral cart if region_id is available
|
|
68
|
+
// This is required because /store/shipping-options STRICTLY requires a cart_id for return options
|
|
69
|
+
if (regionId) {
|
|
70
|
+
try {
|
|
71
|
+
// Create a temporary cart for this region to get valid shipping options
|
|
72
|
+
const { cart } = await sdk.store.cart.create(
|
|
73
|
+
{ region_id: regionId },
|
|
74
|
+
{},
|
|
75
|
+
headers
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (cart?.id) {
|
|
79
|
+
const { shipping_options } = await sdk.client.fetch<HttpTypes.StoreShippingOptionListResponse>(
|
|
80
|
+
`/store/shipping-options`,
|
|
81
|
+
{
|
|
82
|
+
method: "GET",
|
|
83
|
+
query: {
|
|
84
|
+
cart_id: cart.id,
|
|
85
|
+
is_return: true,
|
|
86
|
+
},
|
|
87
|
+
headers,
|
|
88
|
+
next,
|
|
89
|
+
cache: "no-store",
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
return shipping_options as unknown as HttpTypes.StoreShippingOption[]
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// Silence error
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const createReturnRequest = async (
|
|
103
|
+
state: {
|
|
104
|
+
success: boolean
|
|
105
|
+
error: string | null
|
|
106
|
+
return: HttpTypes.StoreReturn | null
|
|
107
|
+
},
|
|
108
|
+
formData: FormData
|
|
109
|
+
): Promise<{
|
|
110
|
+
success: boolean
|
|
111
|
+
error: string | null
|
|
112
|
+
return: HttpTypes.StoreReturn | null
|
|
113
|
+
}> => {
|
|
114
|
+
const orderId = formData.get("order_id") as string
|
|
115
|
+
const returnShippingOptionId = formData.get("return_shipping_option_id") as string
|
|
116
|
+
const itemsJson = formData.get("items") as string
|
|
117
|
+
const note = formData.get("note") as string
|
|
118
|
+
const locationId = formData.get("location_id") as string
|
|
119
|
+
|
|
120
|
+
if (!orderId) return { success: false, error: "Order ID is required", return: null }
|
|
121
|
+
if (!itemsJson) return { success: false, error: "Items are required", return: null }
|
|
122
|
+
|
|
123
|
+
let items: any[] = []
|
|
124
|
+
try {
|
|
125
|
+
items = JSON.parse(itemsJson)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return { success: false, error: "Invalid items data", return: null }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (items.length === 0) {
|
|
131
|
+
return { success: false, error: "At least one item must be selected", return: null }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const headers = {
|
|
135
|
+
...(await getAuthHeaders()),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Standard Medusa v2 payload
|
|
140
|
+
const payload: any = {
|
|
141
|
+
order_id: orderId,
|
|
142
|
+
items: items.map((item) => ({
|
|
143
|
+
id: item.id,
|
|
144
|
+
quantity: item.quantity,
|
|
145
|
+
reason_id: item.return_reason_id || undefined,
|
|
146
|
+
note: (note && note.trim().length > 0) ? note : undefined,
|
|
147
|
+
})),
|
|
148
|
+
...(returnShippingOptionId ? {
|
|
149
|
+
return_shipping: {
|
|
150
|
+
option_id: returnShippingOptionId,
|
|
151
|
+
location_id: locationId || (items[0] as any).location_id || "default_location"
|
|
152
|
+
}
|
|
153
|
+
} : {}),
|
|
154
|
+
// Optional: keep global note if supported
|
|
155
|
+
note: (note && note.trim().length > 0) ? note : "Return request",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Double check location_id is NOT nested incorrectly if already set above
|
|
159
|
+
// The plugin expects return_shipping.location_id
|
|
160
|
+
|
|
161
|
+
// Check for any available guest tokens
|
|
162
|
+
const cookieStore = await cookies()
|
|
163
|
+
const guestToken = cookieStore.get("_medusa_guest_jwt")?.value
|
|
164
|
+
|| cookieStore.get("_medusa_guest_token")?.value
|
|
165
|
+
|| cookieStore.get("guest_id")?.value
|
|
166
|
+
|
|
167
|
+
const token = cookieStore.get("_medusa_jwt")?.value
|
|
168
|
+
|
|
169
|
+
let returnData: HttpTypes.StoreReturn
|
|
170
|
+
|
|
171
|
+
// If we have a guest token, try the guest specific endpoint
|
|
172
|
+
if (guestToken && !token) {
|
|
173
|
+
try {
|
|
174
|
+
const response = await sdk.client.fetch<any>(
|
|
175
|
+
`/store/guest-orders/${orderId}/returns`,
|
|
176
|
+
{
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: payload,
|
|
179
|
+
headers: {
|
|
180
|
+
...headers,
|
|
181
|
+
"Authorization": `Bearer ${guestToken}`
|
|
182
|
+
},
|
|
183
|
+
cache: "no-store",
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
returnData = response.return || response
|
|
187
|
+
} catch (e) {
|
|
188
|
+
// Fallback to standard if guest endpoint fails
|
|
189
|
+
const response = await sdk.client.fetch<{ return: HttpTypes.StoreReturn }>(
|
|
190
|
+
`/store/returns`,
|
|
191
|
+
{
|
|
192
|
+
method: "POST",
|
|
193
|
+
body: payload,
|
|
194
|
+
headers,
|
|
195
|
+
cache: "no-store",
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
returnData = response.return
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Standard flow
|
|
202
|
+
const response = await sdk.client.fetch<{ return: HttpTypes.StoreReturn }>(
|
|
203
|
+
`/store/returns`,
|
|
204
|
+
{
|
|
205
|
+
method: "POST",
|
|
206
|
+
body: payload,
|
|
207
|
+
headers,
|
|
208
|
+
cache: "no-store",
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
returnData = response.return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
revalidateTag("orders")
|
|
215
|
+
|
|
216
|
+
return { success: true, error: null, return: returnData }
|
|
217
|
+
} catch (error: any) {
|
|
218
|
+
return { success: false, error: error.message || "Failed to create return request", return: null }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Link a payment method to a return (for Refund Destination)
|
|
224
|
+
*/
|
|
225
|
+
export const updateReturnPayment = async (returnId: string, paymentId: string) => {
|
|
226
|
+
const headers = {
|
|
227
|
+
...(await getAuthHeaders()),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const response = await sdk.client.fetch<any>(
|
|
232
|
+
`/store/refund-payment-mapping/${returnId}`,
|
|
233
|
+
{
|
|
234
|
+
method: "PUT",
|
|
235
|
+
body: { payment_id: paymentId },
|
|
236
|
+
headers,
|
|
237
|
+
cache: "no-store",
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
return { success: true, data: response }
|
|
241
|
+
} catch (error: any) {
|
|
242
|
+
console.error("Update return payment fail:", error)
|
|
243
|
+
return { success: false, error: error.message }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* List returns for an order or customer
|
|
249
|
+
*/
|
|
250
|
+
export const listReturns = async (orderId?: string) => {
|
|
251
|
+
const headers = {
|
|
252
|
+
...(await getAuthHeaders()),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Authenticated flow
|
|
256
|
+
const authHeaders = headers as any
|
|
257
|
+
if (authHeaders.authorization || authHeaders.Authorization) {
|
|
258
|
+
try {
|
|
259
|
+
const response = await sdk.client.fetch<any>(`/store/returns`, {
|
|
260
|
+
method: "GET",
|
|
261
|
+
query: orderId ? { order_id: orderId } : {},
|
|
262
|
+
headers,
|
|
263
|
+
cache: "no-store",
|
|
264
|
+
})
|
|
265
|
+
return response
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return { returns: [], count: 0 }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Guest flow
|
|
272
|
+
if (orderId) {
|
|
273
|
+
const cookieStore = await cookies()
|
|
274
|
+
const guestToken = cookieStore.get("_medusa_guest_jwt")?.value || cookieStore.get("_medusa_guest_token")?.value
|
|
275
|
+
|
|
276
|
+
if (guestToken) {
|
|
277
|
+
try {
|
|
278
|
+
const response = await sdk.client.fetch<any>(`/store/guest-orders/${orderId}/returns`, {
|
|
279
|
+
method: "GET",
|
|
280
|
+
headers: {
|
|
281
|
+
...headers,
|
|
282
|
+
"Authorization": `Bearer ${guestToken}`
|
|
283
|
+
},
|
|
284
|
+
cache: "no-store",
|
|
285
|
+
})
|
|
286
|
+
return response
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return { returns: [], count: 0 }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { returns: [], count: 0 }
|
|
294
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
|
|
2
|
+
"use server"
|
|
3
|
+
|
|
4
|
+
import { sdk } from "../config"
|
|
5
|
+
import { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import { cookies } from "next/headers"
|
|
7
|
+
import { getAuthHeaders } from "../cookies"
|
|
8
|
+
import { revalidateTag } from "next/cache"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create an exchange (swap) request.
|
|
12
|
+
* Supports both authenticated customers and guest users.
|
|
13
|
+
*/
|
|
14
|
+
export const createSwapRequest = async (
|
|
15
|
+
prevState: any,
|
|
16
|
+
formData: FormData
|
|
17
|
+
): Promise<{
|
|
18
|
+
success: boolean
|
|
19
|
+
error: string | null
|
|
20
|
+
swap: any | null
|
|
21
|
+
}> => {
|
|
22
|
+
const orderId = formData.get("order_id") as string
|
|
23
|
+
const returnItemsJson = formData.get("return_items") as string
|
|
24
|
+
const newItemsJson = formData.get("new_items") as string
|
|
25
|
+
const reason = formData.get("reason") as string
|
|
26
|
+
const note = formData.get("note") as string
|
|
27
|
+
|
|
28
|
+
if (!orderId) return { success: false, error: "Order ID is required", swap: null }
|
|
29
|
+
if (!returnItemsJson) return { success: false, error: "Items to return are required", swap: null }
|
|
30
|
+
if (!newItemsJson) return { success: false, error: "New items are required", swap: null }
|
|
31
|
+
|
|
32
|
+
let returnItems: any[] = []
|
|
33
|
+
let newItems: any[] = []
|
|
34
|
+
try {
|
|
35
|
+
returnItems = JSON.parse(returnItemsJson)
|
|
36
|
+
newItems = JSON.parse(newItemsJson)
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { success: false, error: "Invalid items data format", swap: null }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (returnItems.length === 0) {
|
|
42
|
+
return { success: false, error: "At least one item must be selected for return", swap: null }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const headers = {
|
|
46
|
+
...(await getAuthHeaders()),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const payload = {
|
|
51
|
+
order_id: orderId,
|
|
52
|
+
return_items: returnItems,
|
|
53
|
+
new_items: newItems,
|
|
54
|
+
reason: reason || "Size exchange",
|
|
55
|
+
note: note || "Exchange requested from storefront",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cookieStore = await cookies()
|
|
59
|
+
const guestToken = cookieStore.get("_medusa_guest_jwt")?.value || cookieStore.get("_medusa_guest_token")?.value
|
|
60
|
+
const token = cookieStore.get("_medusa_jwt")?.value
|
|
61
|
+
|
|
62
|
+
let swapData: any
|
|
63
|
+
|
|
64
|
+
// Check if we should use the guest endpoint
|
|
65
|
+
if (guestToken && !token) {
|
|
66
|
+
|
|
67
|
+
const response = await sdk.client.fetch<any>(
|
|
68
|
+
`/store/guest-orders/${orderId}/swaps`,
|
|
69
|
+
{
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: {
|
|
72
|
+
return_items: payload.return_items,
|
|
73
|
+
new_items: payload.new_items,
|
|
74
|
+
reason: payload.reason,
|
|
75
|
+
note: payload.note,
|
|
76
|
+
},
|
|
77
|
+
headers: {
|
|
78
|
+
...headers,
|
|
79
|
+
"Authorization": `Bearer ${guestToken}`
|
|
80
|
+
},
|
|
81
|
+
cache: "no-store",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
swapData = response.swap || response
|
|
85
|
+
} else {
|
|
86
|
+
|
|
87
|
+
const response = await sdk.client.fetch<any>(
|
|
88
|
+
`/store/swaps`,
|
|
89
|
+
{
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: payload,
|
|
92
|
+
headers,
|
|
93
|
+
cache: "no-store",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
swapData = response.swap || response
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
revalidateTag("orders")
|
|
100
|
+
|
|
101
|
+
return { success: true, error: null, swap: swapData }
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
// Extract error message from response if possible
|
|
104
|
+
let errorMsg = "Failed to create exchange request"
|
|
105
|
+
if (error.response?.data?.message) {
|
|
106
|
+
errorMsg = error.response.data.message
|
|
107
|
+
} else if (error.message) {
|
|
108
|
+
errorMsg = error.message
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { success: false, error: errorMsg, swap: null }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* List swaps for an order or customer
|
|
117
|
+
*/
|
|
118
|
+
export const listSwaps = async (orderId?: string) => {
|
|
119
|
+
const headers = {
|
|
120
|
+
...(await getAuthHeaders()),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Authenticated flow
|
|
124
|
+
if ((headers as any).authorization) {
|
|
125
|
+
return sdk.client.fetch<any>(`/store/swaps`, {
|
|
126
|
+
method: "GET",
|
|
127
|
+
query: orderId ? { order_id: orderId } : {},
|
|
128
|
+
headers,
|
|
129
|
+
cache: "no-store",
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Guest flow
|
|
134
|
+
if (orderId) {
|
|
135
|
+
const cookieStore = await cookies()
|
|
136
|
+
const guestToken = cookieStore.get("_medusa_guest_jwt")?.value || cookieStore.get("_medusa_guest_token")?.value
|
|
137
|
+
if (guestToken) {
|
|
138
|
+
return sdk.client.fetch<any>(`/store/guest-orders/${orderId}/swaps`, {
|
|
139
|
+
method: "GET",
|
|
140
|
+
headers: {
|
|
141
|
+
...headers,
|
|
142
|
+
"Authorization": `Bearer ${guestToken}`
|
|
143
|
+
},
|
|
144
|
+
cache: "no-store",
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { swaps: [], count: 0 }
|
|
150
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { sdk } from "../config"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
|
|
6
|
+
import { getAuthHeaders, getCacheOptions } from "../cookies"
|
|
7
|
+
|
|
8
|
+
export const retrieveVariant = async (
|
|
9
|
+
variant_id: string
|
|
10
|
+
): Promise<HttpTypes.StoreProductVariant | null> => {
|
|
11
|
+
const authHeaders = await getAuthHeaders()
|
|
12
|
+
|
|
13
|
+
if (!authHeaders) return null
|
|
14
|
+
|
|
15
|
+
const headers = {
|
|
16
|
+
...authHeaders,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const next = {
|
|
20
|
+
...(await getCacheOptions("variants")),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return await sdk.client
|
|
24
|
+
.fetch<{ variant: HttpTypes.StoreProductVariant }>(
|
|
25
|
+
`/store/product-variants/${variant_id}`,
|
|
26
|
+
{
|
|
27
|
+
method: "GET",
|
|
28
|
+
query: {
|
|
29
|
+
fields: "*images",
|
|
30
|
+
},
|
|
31
|
+
headers,
|
|
32
|
+
next,
|
|
33
|
+
cache: "force-cache",
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
.then(({ variant }) => variant)
|
|
37
|
+
.catch(() => null)
|
|
38
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { HttpTypes } from "@medusajs/types";
|
|
4
|
+
import { sdk } from "../config";
|
|
5
|
+
import { getRegion } from "./regions";
|
|
6
|
+
import { fetchWishlist } from "medusa-wishlist-logic/server";
|
|
7
|
+
|
|
8
|
+
/** Wishlist entries enriched with full Medusa product payloads. */
|
|
9
|
+
export async function getWishlist(includeDetails = true, countryCode?: string) {
|
|
10
|
+
const result = await fetchWishlist(includeDetails);
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
return { success: false as const, error: result.error, data: [] as unknown[] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let wishlistItems = result.data as Array<Record<string, unknown>>;
|
|
16
|
+
|
|
17
|
+
if (!includeDetails || wishlistItems.length === 0) {
|
|
18
|
+
return { success: true as const, data: wishlistItems };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const productIds = wishlistItems
|
|
22
|
+
.map((item) => {
|
|
23
|
+
const id = item.product_id ?? item.id;
|
|
24
|
+
return typeof id === "string" ? id : null;
|
|
25
|
+
})
|
|
26
|
+
.filter((id): id is string => Boolean(id));
|
|
27
|
+
|
|
28
|
+
if (productIds.length === 0) {
|
|
29
|
+
return { success: true as const, data: wishlistItems };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
let regionId: string | undefined;
|
|
34
|
+
if (countryCode) {
|
|
35
|
+
const region = await getRegion(countryCode);
|
|
36
|
+
regionId = region?.id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const productsResponse = await sdk.store.product.list(
|
|
40
|
+
{
|
|
41
|
+
id: productIds,
|
|
42
|
+
fields:
|
|
43
|
+
"*thumbnail,*images,*variants,*variants.options,*options,*options.values,*variants.calculated_price,*variants.inventory_quantity,*variants.manage_inventory,*variants.allow_backorder",
|
|
44
|
+
...(regionId ? { region_id: regionId } : {}),
|
|
45
|
+
},
|
|
46
|
+
{ next: { tags: ["products"] } }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const fullProducts = productsResponse.products as HttpTypes.StoreProduct[];
|
|
50
|
+
|
|
51
|
+
wishlistItems = wishlistItems.map((item) => {
|
|
52
|
+
const itemId = (item.product_id ?? item.id) as string;
|
|
53
|
+
const fullProduct = fullProducts.find((p) => p.id === itemId);
|
|
54
|
+
if (fullProduct) {
|
|
55
|
+
return { ...item, product: fullProduct };
|
|
56
|
+
}
|
|
57
|
+
return item;
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
// Return API wishlist without enrichment
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { success: true as const, data: wishlistItems };
|
|
64
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Edge-safe Medusa store fetches (no Node SDK). */
|
|
2
|
+
|
|
3
|
+
export interface MedusaMiddlewareClientOptions {
|
|
4
|
+
backendUrl: string;
|
|
5
|
+
publishableApiKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeBackendUrl(backendUrl: string): string {
|
|
9
|
+
return backendUrl.replace(/\/$/, "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function medusaListRegions(
|
|
13
|
+
options: MedusaMiddlewareClientOptions,
|
|
14
|
+
init?: RequestInit
|
|
15
|
+
): Promise<{ regions: unknown[] }> {
|
|
16
|
+
const url = `${normalizeBackendUrl(options.backendUrl)}/store/regions`;
|
|
17
|
+
const response = await fetch(url, {
|
|
18
|
+
...init,
|
|
19
|
+
headers: {
|
|
20
|
+
"x-publishable-api-key": options.publishableApiKey,
|
|
21
|
+
...(init?.headers as Record<string, string> | undefined),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const json = (await response.json()) as { regions?: unknown[]; message?: string };
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(json.message || "Failed to fetch regions");
|
|
28
|
+
}
|
|
29
|
+
return { regions: json.regions ?? [] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function medusaMergeCarts(
|
|
33
|
+
options: MedusaMiddlewareClientOptions & {
|
|
34
|
+
authorization: string;
|
|
35
|
+
cartId?: string;
|
|
36
|
+
},
|
|
37
|
+
init?: RequestInit
|
|
38
|
+
): Promise<Response> {
|
|
39
|
+
const url = `${normalizeBackendUrl(options.backendUrl)}/store/carts/merge`;
|
|
40
|
+
return fetch(url, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
...init,
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
"x-publishable-api-key": options.publishableApiKey,
|
|
46
|
+
Authorization: options.authorization.startsWith("Bearer ")
|
|
47
|
+
? options.authorization
|
|
48
|
+
: `Bearer ${options.authorization}`,
|
|
49
|
+
...(init?.headers as Record<string, string> | undefined),
|
|
50
|
+
},
|
|
51
|
+
body: options.cartId ? JSON.stringify({ cart_id: options.cartId }) : init?.body,
|
|
52
|
+
cache: init?.cache ?? "no-store",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default function medusaError(error: any): never {
|
|
2
|
+
if (error.response) {
|
|
3
|
+
// The request was made and the server responded with a status code
|
|
4
|
+
// that falls out of the range of 2xx
|
|
5
|
+
const u = new URL(error.config.url, error.config.baseURL)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Extracting the error message from the response data
|
|
9
|
+
const message = error.response.data.message || error.response.data
|
|
10
|
+
|
|
11
|
+
throw new Error(message.charAt(0).toUpperCase() + message.slice(1) + ".")
|
|
12
|
+
} else if (error.request) {
|
|
13
|
+
// The request was made but no response was received
|
|
14
|
+
throw new Error("No response received: " + error.request)
|
|
15
|
+
} else {
|
|
16
|
+
// Something happened in setting up the request that triggered an Error
|
|
17
|
+
throw new Error("Error setting up the request: " + error.message)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HttpTypes } from "@medusajs/types";
|
|
2
|
+
|
|
3
|
+
export type SortOptions =
|
|
4
|
+
| "price_asc"
|
|
5
|
+
| "price_desc"
|
|
6
|
+
| "created_at_desc"
|
|
7
|
+
| "created_at_asc"
|
|
8
|
+
| "bestsellers"
|
|
9
|
+
| "created_at";
|
|
10
|
+
|
|
11
|
+
interface MinPricedProduct extends HttpTypes.StoreProduct {
|
|
12
|
+
_minPrice?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function sortProducts(
|
|
16
|
+
products: HttpTypes.StoreProduct[],
|
|
17
|
+
sortBy: SortOptions
|
|
18
|
+
): HttpTypes.StoreProduct[] {
|
|
19
|
+
const sortedProducts = products as MinPricedProduct[];
|
|
20
|
+
|
|
21
|
+
if (["price_asc", "price_desc"].includes(sortBy)) {
|
|
22
|
+
sortedProducts.forEach((product) => {
|
|
23
|
+
if (product.variants && product.variants.length > 0) {
|
|
24
|
+
product._minPrice = Math.min(
|
|
25
|
+
...product.variants.map(
|
|
26
|
+
(variant) => variant?.calculated_price?.calculated_amount || 0
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
} else {
|
|
30
|
+
product._minPrice = Infinity;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
sortedProducts.sort((a, b) => {
|
|
35
|
+
const diff = a._minPrice! - b._minPrice!;
|
|
36
|
+
return sortBy === "price_asc" ? diff : -diff;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (sortBy === "created_at") {
|
|
41
|
+
sortedProducts.sort((a, b) => {
|
|
42
|
+
return new Date(b.created_at!).getTime() - new Date(a.created_at!).getTime();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return sortedProducts;
|
|
47
|
+
}
|