@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.
Files changed (82) hide show
  1. package/README.md +15 -0
  2. package/package.json +70 -0
  3. package/src/analytics/ga4-ecommerce.ts +96 -0
  4. package/src/config.ts +36 -0
  5. package/src/constants.tsx +84 -0
  6. package/src/context/modal-context.tsx +40 -0
  7. package/src/context/wishlist-context.tsx +96 -0
  8. package/src/data/cart/abandoned.ts +111 -0
  9. package/src/data/cart/buyNow.ts +184 -0
  10. package/src/data/cart/checkout.ts +487 -0
  11. package/src/data/cart/index.ts +7 -0
  12. package/src/data/cart/mutations.ts +189 -0
  13. package/src/data/cart/promotions.ts +121 -0
  14. package/src/data/cart/region.ts +66 -0
  15. package/src/data/cart/retrieve.ts +162 -0
  16. package/src/data/categories.ts +90 -0
  17. package/src/data/collections.ts +109 -0
  18. package/src/data/contact.ts +143 -0
  19. package/src/data/cookies.ts +170 -0
  20. package/src/data/customer-registration.ts +365 -0
  21. package/src/data/customer.ts +638 -0
  22. package/src/data/dynamic-config.ts +420 -0
  23. package/src/data/fulfillment.ts +95 -0
  24. package/src/data/guest.ts +357 -0
  25. package/src/data/locale-actions.ts +74 -0
  26. package/src/data/locales.ts +28 -0
  27. package/src/data/newsletter.ts +41 -0
  28. package/src/data/notifications.ts +22 -0
  29. package/src/data/onboarding.ts +9 -0
  30. package/src/data/orders.ts +500 -0
  31. package/src/data/payment-details.ts +68 -0
  32. package/src/data/payment.ts +32 -0
  33. package/src/data/products.ts +424 -0
  34. package/src/data/regions.ts +64 -0
  35. package/src/data/returns.ts +305 -0
  36. package/src/data/reviews.ts +279 -0
  37. package/src/data/swaps.ts +154 -0
  38. package/src/data/variants.ts +38 -0
  39. package/src/data/wishlist.ts +292 -0
  40. package/src/domain/cart/abandoned-carts.ts +49 -0
  41. package/src/domain/cart/buy-now.ts +15 -0
  42. package/src/domain/cart/checkout.ts +25 -0
  43. package/src/domain/cart/index.ts +8 -0
  44. package/src/domain/cart/metadata.ts +21 -0
  45. package/src/domain/cart/payment.ts +21 -0
  46. package/src/domain/cart/phone.ts +17 -0
  47. package/src/domain/cart/reorder.ts +19 -0
  48. package/src/domain/cart/validation.ts +43 -0
  49. package/src/domain/product/pricing.ts +49 -0
  50. package/src/domain/product/variant-selection.ts +193 -0
  51. package/src/firebase.ts +48 -0
  52. package/src/hooks/index.ts +8 -0
  53. package/src/hooks/use-add-to-cart.ts +63 -0
  54. package/src/hooks/use-cart.ts +132 -0
  55. package/src/hooks/use-checkout.ts +62 -0
  56. package/src/hooks/use-in-view.tsx +29 -0
  57. package/src/hooks/use-product-actions.ts +190 -0
  58. package/src/hooks/use-product-reviews.ts +18 -0
  59. package/src/hooks/use-product-variant.ts +142 -0
  60. package/src/hooks/use-server-action.ts +30 -0
  61. package/src/hooks/use-toggle-state.tsx +46 -0
  62. package/src/hooks/use-wishlist.ts +3 -0
  63. package/src/theme/inline-vars.ts +12 -0
  64. package/src/types/account.ts +21 -0
  65. package/src/types/cart.ts +13 -0
  66. package/src/types/home.ts +52 -0
  67. package/src/types/layout.ts +29 -0
  68. package/src/types/product-card.ts +17 -0
  69. package/src/util/compare-addresses.ts +28 -0
  70. package/src/util/env.ts +3 -0
  71. package/src/util/get-locale-header.ts +8 -0
  72. package/src/util/get-percentage-diff.ts +6 -0
  73. package/src/util/get-product-price.ts +78 -0
  74. package/src/util/google-oauth.ts +28 -0
  75. package/src/util/isEmpty.ts +11 -0
  76. package/src/util/medusa-error.ts +18 -0
  77. package/src/util/money.ts +26 -0
  78. package/src/util/order-status.tsx +179 -0
  79. package/src/util/product.ts +431 -0
  80. package/src/util/repeat.ts +5 -0
  81. package/src/util/returns.ts +71 -0
  82. 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
+ }