@pradip1995/commerce-core 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/README.md +15 -0
- package/package.json +70 -0
- package/src/analytics/ga4-ecommerce.ts +96 -0
- package/src/config.ts +36 -0
- package/src/constants.tsx +84 -0
- package/src/context/modal-context.tsx +40 -0
- package/src/context/wishlist-context.tsx +96 -0
- package/src/data/cart/abandoned.ts +111 -0
- package/src/data/cart/buyNow.ts +184 -0
- package/src/data/cart/checkout.ts +487 -0
- package/src/data/cart/index.ts +7 -0
- package/src/data/cart/mutations.ts +189 -0
- package/src/data/cart/promotions.ts +121 -0
- package/src/data/cart/region.ts +66 -0
- package/src/data/cart/retrieve.ts +162 -0
- package/src/data/categories.ts +90 -0
- package/src/data/collections.ts +109 -0
- package/src/data/contact.ts +143 -0
- package/src/data/cookies.ts +170 -0
- package/src/data/customer-registration.ts +365 -0
- package/src/data/customer.ts +638 -0
- package/src/data/dynamic-config.ts +420 -0
- package/src/data/fulfillment.ts +95 -0
- package/src/data/guest.ts +357 -0
- package/src/data/locale-actions.ts +74 -0
- package/src/data/locales.ts +28 -0
- package/src/data/newsletter.ts +41 -0
- package/src/data/notifications.ts +22 -0
- package/src/data/onboarding.ts +9 -0
- package/src/data/orders.ts +500 -0
- package/src/data/payment-details.ts +68 -0
- package/src/data/payment.ts +32 -0
- package/src/data/products.ts +424 -0
- package/src/data/regions.ts +64 -0
- package/src/data/returns.ts +305 -0
- package/src/data/reviews.ts +279 -0
- package/src/data/swaps.ts +154 -0
- package/src/data/variants.ts +38 -0
- package/src/data/wishlist.ts +292 -0
- package/src/domain/cart/abandoned-carts.ts +49 -0
- package/src/domain/cart/buy-now.ts +15 -0
- package/src/domain/cart/checkout.ts +25 -0
- package/src/domain/cart/index.ts +8 -0
- package/src/domain/cart/metadata.ts +21 -0
- package/src/domain/cart/payment.ts +21 -0
- package/src/domain/cart/phone.ts +17 -0
- package/src/domain/cart/reorder.ts +19 -0
- package/src/domain/cart/validation.ts +43 -0
- package/src/domain/product/pricing.ts +49 -0
- package/src/domain/product/variant-selection.ts +193 -0
- package/src/firebase.ts +48 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/use-add-to-cart.ts +63 -0
- package/src/hooks/use-cart.ts +132 -0
- package/src/hooks/use-checkout.ts +62 -0
- package/src/hooks/use-in-view.tsx +29 -0
- package/src/hooks/use-product-actions.ts +190 -0
- package/src/hooks/use-product-reviews.ts +18 -0
- package/src/hooks/use-product-variant.ts +142 -0
- package/src/hooks/use-server-action.ts +30 -0
- package/src/hooks/use-toggle-state.tsx +46 -0
- package/src/hooks/use-wishlist.ts +3 -0
- package/src/theme/inline-vars.ts +12 -0
- package/src/types/account.ts +21 -0
- package/src/types/cart.ts +13 -0
- package/src/types/home.ts +52 -0
- package/src/types/layout.ts +29 -0
- package/src/types/product-card.ts +17 -0
- package/src/util/compare-addresses.ts +28 -0
- package/src/util/env.ts +3 -0
- package/src/util/get-locale-header.ts +8 -0
- package/src/util/get-percentage-diff.ts +6 -0
- package/src/util/get-product-price.ts +78 -0
- package/src/util/google-oauth.ts +28 -0
- package/src/util/isEmpty.ts +11 -0
- package/src/util/medusa-error.ts +18 -0
- package/src/util/money.ts +26 -0
- package/src/util/order-status.tsx +179 -0
- package/src/util/product.ts +431 -0
- package/src/util/repeat.ts +5 -0
- package/src/util/returns.ts +71 -0
- package/src/util/sort-products.ts +48 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { sdk } from "@core/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 } =
|
|
46
|
+
await sdk.client.fetch<HttpTypes.StoreShippingOptionListResponse>(
|
|
47
|
+
`/store/shipping-options`,
|
|
48
|
+
{
|
|
49
|
+
method: "GET",
|
|
50
|
+
query: {
|
|
51
|
+
cart_id: cartId,
|
|
52
|
+
is_return: true,
|
|
53
|
+
},
|
|
54
|
+
headers,
|
|
55
|
+
next,
|
|
56
|
+
cache: "no-store",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (shipping_options.length > 0) {
|
|
61
|
+
return shipping_options as unknown as HttpTypes.StoreShippingOption[]
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Fallback strategy
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Strategy 2: Fallback - Create ephemeral cart if region_id is available
|
|
69
|
+
// This is required because /store/shipping-options STRICTLY requires a cart_id for return options
|
|
70
|
+
if (regionId) {
|
|
71
|
+
try {
|
|
72
|
+
// Create a temporary cart for this region to get valid shipping options
|
|
73
|
+
const { cart } = await sdk.store.cart.create({ region_id: regionId }, {}, headers)
|
|
74
|
+
|
|
75
|
+
if (cart?.id) {
|
|
76
|
+
const { shipping_options } =
|
|
77
|
+
await sdk.client.fetch<HttpTypes.StoreShippingOptionListResponse>(
|
|
78
|
+
`/store/shipping-options`,
|
|
79
|
+
{
|
|
80
|
+
method: "GET",
|
|
81
|
+
query: {
|
|
82
|
+
cart_id: cart.id,
|
|
83
|
+
is_return: true,
|
|
84
|
+
},
|
|
85
|
+
headers,
|
|
86
|
+
next,
|
|
87
|
+
cache: "no-store",
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
return shipping_options as unknown as HttpTypes.StoreShippingOption[]
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// Silence error
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const createReturnRequest = async (
|
|
101
|
+
state: {
|
|
102
|
+
success: boolean
|
|
103
|
+
error: string | null
|
|
104
|
+
return: HttpTypes.StoreReturn | null
|
|
105
|
+
},
|
|
106
|
+
formData: FormData
|
|
107
|
+
): Promise<{
|
|
108
|
+
success: boolean
|
|
109
|
+
error: string | null
|
|
110
|
+
return: HttpTypes.StoreReturn | null
|
|
111
|
+
}> => {
|
|
112
|
+
const orderId = formData.get("order_id") as string
|
|
113
|
+
const returnShippingOptionId = formData.get("return_shipping_option_id") as string
|
|
114
|
+
const itemsJson = formData.get("items") as string
|
|
115
|
+
const note = formData.get("note") as string
|
|
116
|
+
const locationId = formData.get("location_id") as string
|
|
117
|
+
|
|
118
|
+
if (!orderId) return { success: false, error: "Order ID is required", return: null }
|
|
119
|
+
if (!itemsJson) return { success: false, error: "Items are required", return: null }
|
|
120
|
+
|
|
121
|
+
let items: any[] = []
|
|
122
|
+
try {
|
|
123
|
+
items = JSON.parse(itemsJson)
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return { success: false, error: "Invalid items data", return: null }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (items.length === 0) {
|
|
129
|
+
return { success: false, error: "At least one item must be selected", return: null }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const headers = {
|
|
133
|
+
...(await getAuthHeaders()),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Standard Medusa v2 payload
|
|
138
|
+
const payload: any = {
|
|
139
|
+
order_id: orderId,
|
|
140
|
+
items: items.map((item) => ({
|
|
141
|
+
id: item.id,
|
|
142
|
+
quantity: item.quantity,
|
|
143
|
+
reason_id: item.return_reason_id || undefined,
|
|
144
|
+
note: note && note.trim().length > 0 ? note : undefined,
|
|
145
|
+
})),
|
|
146
|
+
...(returnShippingOptionId
|
|
147
|
+
? {
|
|
148
|
+
return_shipping: {
|
|
149
|
+
option_id: returnShippingOptionId,
|
|
150
|
+
location_id:
|
|
151
|
+
locationId || (items[0] as any).location_id || "default_location",
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
// Optional: keep global note if supported
|
|
156
|
+
note: note && note.trim().length > 0 ? note : "Return request",
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Double check location_id is NOT nested incorrectly if already set above
|
|
160
|
+
// The plugin expects return_shipping.location_id
|
|
161
|
+
|
|
162
|
+
// Check for any available guest tokens
|
|
163
|
+
const cookieStore = await cookies()
|
|
164
|
+
const guestToken =
|
|
165
|
+
cookieStore.get("_medusa_guest_jwt")?.value ||
|
|
166
|
+
cookieStore.get("_medusa_guest_token")?.value ||
|
|
167
|
+
cookieStore.get("guest_id")?.value
|
|
168
|
+
|
|
169
|
+
const token = cookieStore.get("_medusa_jwt")?.value
|
|
170
|
+
|
|
171
|
+
let returnData: HttpTypes.StoreReturn
|
|
172
|
+
|
|
173
|
+
// If we have a guest token, try the guest specific endpoint
|
|
174
|
+
if (guestToken && !token) {
|
|
175
|
+
try {
|
|
176
|
+
const response = await sdk.client.fetch<any>(
|
|
177
|
+
`/store/guest-orders/${orderId}/returns`,
|
|
178
|
+
{
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: payload,
|
|
181
|
+
headers: {
|
|
182
|
+
...headers,
|
|
183
|
+
Authorization: `Bearer ${guestToken}`,
|
|
184
|
+
},
|
|
185
|
+
cache: "no-store",
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
returnData = response.return || response
|
|
189
|
+
} catch (e) {
|
|
190
|
+
// Fallback to standard if guest endpoint fails
|
|
191
|
+
const response = await sdk.client.fetch<{ return: HttpTypes.StoreReturn }>(
|
|
192
|
+
`/store/returns`,
|
|
193
|
+
{
|
|
194
|
+
method: "POST",
|
|
195
|
+
body: payload,
|
|
196
|
+
headers,
|
|
197
|
+
cache: "no-store",
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
returnData = response.return
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// Standard flow
|
|
204
|
+
const response = await sdk.client.fetch<{ return: HttpTypes.StoreReturn }>(
|
|
205
|
+
`/store/returns`,
|
|
206
|
+
{
|
|
207
|
+
method: "POST",
|
|
208
|
+
body: payload,
|
|
209
|
+
headers,
|
|
210
|
+
cache: "no-store",
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
returnData = response.return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
revalidateTag("orders")
|
|
217
|
+
|
|
218
|
+
return { success: true, error: null, return: returnData }
|
|
219
|
+
} catch (error: any) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: error.message || "Failed to create return request",
|
|
223
|
+
return: null,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Link a payment method to a return (for Refund Destination)
|
|
230
|
+
*/
|
|
231
|
+
export const updateReturnPayment = async (returnId: string, paymentId: string) => {
|
|
232
|
+
const headers = {
|
|
233
|
+
...(await getAuthHeaders()),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const response = await sdk.client.fetch<any>(
|
|
238
|
+
`/store/refund-payment-mapping/${returnId}`,
|
|
239
|
+
{
|
|
240
|
+
method: "PUT",
|
|
241
|
+
body: { payment_id: paymentId },
|
|
242
|
+
headers,
|
|
243
|
+
cache: "no-store",
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
return { success: true, data: response }
|
|
247
|
+
} catch (error: any) {
|
|
248
|
+
console.error("Update return payment fail:", error)
|
|
249
|
+
return { success: false, error: error.message }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* List returns for an order or customer
|
|
255
|
+
*/
|
|
256
|
+
export const listReturns = async (orderId?: string) => {
|
|
257
|
+
const headers = {
|
|
258
|
+
...(await getAuthHeaders()),
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Authenticated flow
|
|
262
|
+
const authHeaders = headers as any
|
|
263
|
+
if (authHeaders.authorization || authHeaders.Authorization) {
|
|
264
|
+
try {
|
|
265
|
+
const response = await sdk.client.fetch<any>(`/store/returns`, {
|
|
266
|
+
method: "GET",
|
|
267
|
+
query: orderId ? { order_id: orderId } : {},
|
|
268
|
+
headers,
|
|
269
|
+
cache: "no-store",
|
|
270
|
+
})
|
|
271
|
+
return response
|
|
272
|
+
} catch (e) {
|
|
273
|
+
return { returns: [], count: 0 }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Guest flow
|
|
278
|
+
if (orderId) {
|
|
279
|
+
const cookieStore = await cookies()
|
|
280
|
+
const guestToken =
|
|
281
|
+
cookieStore.get("_medusa_guest_jwt")?.value ||
|
|
282
|
+
cookieStore.get("_medusa_guest_token")?.value
|
|
283
|
+
|
|
284
|
+
if (guestToken) {
|
|
285
|
+
try {
|
|
286
|
+
const response = await sdk.client.fetch<any>(
|
|
287
|
+
`/store/guest-orders/${orderId}/returns`,
|
|
288
|
+
{
|
|
289
|
+
method: "GET",
|
|
290
|
+
headers: {
|
|
291
|
+
...headers,
|
|
292
|
+
Authorization: `Bearer ${guestToken}`,
|
|
293
|
+
},
|
|
294
|
+
cache: "no-store",
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
return response
|
|
298
|
+
} catch (e) {
|
|
299
|
+
return { returns: [], count: 0 }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { returns: [], count: 0 }
|
|
305
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { sdk } from "@core/config"
|
|
4
|
+
import { getAuthHeaders } from "./cookies"
|
|
5
|
+
|
|
6
|
+
type SubmitReviewParams = {
|
|
7
|
+
product_id: string
|
|
8
|
+
rating: number
|
|
9
|
+
title: string
|
|
10
|
+
description: string
|
|
11
|
+
images?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function submitReview(params: SubmitReviewParams) {
|
|
15
|
+
try {
|
|
16
|
+
// Server actions kabhi–kabhi stringified JSON bhi receive kar sakte hain.
|
|
17
|
+
// Defensive: agar string mile to pehle parse karke proper object bana lo.
|
|
18
|
+
const normalizedParams: SubmitReviewParams =
|
|
19
|
+
typeof params === "string" ? JSON.parse(params) : params
|
|
20
|
+
|
|
21
|
+
const authHeaders = await getAuthHeaders()
|
|
22
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
23
|
+
|
|
24
|
+
if (!publishableKey) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: "Configuration error: Publishable API key is missing.",
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!authHeaders || !("authorization" in authHeaders)) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: "Please login to submit a review.",
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get base URL from environment
|
|
39
|
+
const baseUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
40
|
+
|
|
41
|
+
const headers: Record<string, string> = {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
"x-publishable-api-key": publishableKey,
|
|
44
|
+
...authHeaders,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await fetch(`${baseUrl}/store/reviews`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers,
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
product_id: normalizedParams.product_id,
|
|
52
|
+
rating: normalizedParams.rating,
|
|
53
|
+
title: normalizedParams.title,
|
|
54
|
+
description: normalizedParams.description,
|
|
55
|
+
images: normalizedParams.images || [],
|
|
56
|
+
}),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const error = await response.json().catch(() => ({ message: "Unknown error" }))
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: error.message || `HTTP ${response.status}: Failed to submit review`,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data = await response.json()
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
data,
|
|
71
|
+
}
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error:
|
|
76
|
+
error.message ||
|
|
77
|
+
error.error?.message ||
|
|
78
|
+
"An error occurred while submitting the review",
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type UpdateReviewParams = {
|
|
84
|
+
id: string
|
|
85
|
+
rating?: number
|
|
86
|
+
title?: string
|
|
87
|
+
description?: string
|
|
88
|
+
images?: string[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function updateReview(params: UpdateReviewParams) {
|
|
92
|
+
try {
|
|
93
|
+
const normalizedParams: UpdateReviewParams =
|
|
94
|
+
typeof params === "string" ? JSON.parse(params) : params
|
|
95
|
+
|
|
96
|
+
const authHeaders = await getAuthHeaders()
|
|
97
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
98
|
+
|
|
99
|
+
if (!publishableKey) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: "Configuration error: Publishable API key is missing.",
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!authHeaders || !("authorization" in authHeaders)) {
|
|
107
|
+
return { success: false, error: "Please login to update your review." }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const baseUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
111
|
+
|
|
112
|
+
const headers: Record<string, string> = {
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
"x-publishable-api-key": publishableKey,
|
|
115
|
+
...authHeaders,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const response = await fetch(`${baseUrl}/store/reviews/${normalizedParams.id}`, {
|
|
119
|
+
method: "PUT",
|
|
120
|
+
headers,
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
rating: normalizedParams.rating,
|
|
123
|
+
title: normalizedParams.title,
|
|
124
|
+
description: normalizedParams.description,
|
|
125
|
+
images: normalizedParams.images,
|
|
126
|
+
}),
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const error = await response.json().catch(() => ({ message: "Unknown error" }))
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: error.message || `HTTP ${response.status}: Failed to update review`,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const data = await response.json()
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
data,
|
|
141
|
+
}
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error:
|
|
146
|
+
error.message ||
|
|
147
|
+
error.error?.message ||
|
|
148
|
+
"An error occurred while updating the review",
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// SERVER ACTION: Fetch Reviews on the Server (Logs will appear in Terminal)
|
|
154
|
+
export async function fetchReviewsForProduct(productId: string) {
|
|
155
|
+
const backendUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
156
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
if (!publishableKey) return []
|
|
160
|
+
|
|
161
|
+
const url = new URL(`${backendUrl}/store/products/${productId}/reviews`)
|
|
162
|
+
url.searchParams.set("status", "approved")
|
|
163
|
+
url.searchParams.set("limit", "50")
|
|
164
|
+
|
|
165
|
+
const response = await fetch(url.toString(), {
|
|
166
|
+
method: "GET",
|
|
167
|
+
headers: {
|
|
168
|
+
"Content-Type": "application/json",
|
|
169
|
+
"x-publishable-api-key": publishableKey,
|
|
170
|
+
},
|
|
171
|
+
cache: "no-store",
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
if (response.ok) {
|
|
175
|
+
const data = await response.json()
|
|
176
|
+
return data.reviews || []
|
|
177
|
+
}
|
|
178
|
+
return []
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check if current authenticated customer has already reviewed the product
|
|
185
|
+
export async function checkCustomerReviewStatus(productId: string) {
|
|
186
|
+
try {
|
|
187
|
+
const authHeaders = await getAuthHeaders()
|
|
188
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
189
|
+
const backendUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
190
|
+
|
|
191
|
+
if (!publishableKey || !authHeaders || !("authorization" in authHeaders)) {
|
|
192
|
+
return { hasReviewed: false }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const response = await fetch(
|
|
196
|
+
`${backendUrl}/store/products/${productId}/reviews/me`,
|
|
197
|
+
{
|
|
198
|
+
method: "GET",
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
"x-publishable-api-key": publishableKey,
|
|
202
|
+
...authHeaders,
|
|
203
|
+
},
|
|
204
|
+
cache: "no-store",
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if (response.ok) {
|
|
209
|
+
const data = await response.json()
|
|
210
|
+
return {
|
|
211
|
+
hasReviewed:
|
|
212
|
+
data.hasReviewed || (data.summary && data.summary.total_reviews > 0),
|
|
213
|
+
reviews: data.reviews || [],
|
|
214
|
+
canAddReview: data.canAddReview,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { hasReviewed: false }
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return { hasReviewed: false }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function fetchRatings() {
|
|
225
|
+
const backendUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
226
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
if (!publishableKey) return []
|
|
230
|
+
|
|
231
|
+
const response = await fetch(`${backendUrl}/store/ratings`, {
|
|
232
|
+
method: "GET",
|
|
233
|
+
headers: {
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
"x-publishable-api-key": publishableKey,
|
|
236
|
+
},
|
|
237
|
+
cache: "no-store",
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (response.ok) {
|
|
241
|
+
const data = await response.json()
|
|
242
|
+
return data.rating || []
|
|
243
|
+
}
|
|
244
|
+
return []
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return []
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
export async function fetchRatingForProduct(productId: string) {
|
|
250
|
+
const backendUrl = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
|
|
251
|
+
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
if (!publishableKey) return null
|
|
255
|
+
|
|
256
|
+
const response = await fetch(
|
|
257
|
+
`${backendUrl}/store/ratings?product_id=${productId}`,
|
|
258
|
+
{
|
|
259
|
+
method: "GET",
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
"x-publishable-api-key": publishableKey,
|
|
263
|
+
},
|
|
264
|
+
cache: "no-store",
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (response.ok) {
|
|
269
|
+
const data = await response.json()
|
|
270
|
+
// The API returns { rating: [...] }
|
|
271
|
+
if (data.rating && data.rating.length > 0) {
|
|
272
|
+
return data.rating[0]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return null
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return null
|
|
278
|
+
}
|
|
279
|
+
}
|