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,581 @@
1
+ "use server"
2
+
3
+ import { sdk } from "../config"
4
+ import medusaError from "../util/medusa-error"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { revalidateTag } from "next/cache"
7
+ import { redirect } from "next/navigation"
8
+ import { cookies } from "next/headers"
9
+ import {
10
+ getAuthHeaders,
11
+ getCacheOptions,
12
+ getCacheTag,
13
+ getCartId,
14
+ removeAuthToken,
15
+ removeCartId,
16
+ removeSyncLock,
17
+ setAuthToken,
18
+ setCartId,
19
+ } from "../cookies"
20
+
21
+ import { cache } from "react"
22
+
23
+ export const retrieveCustomer = cache(
24
+ async (): Promise<HttpTypes.StoreCustomer | null> => {
25
+ const authHeaders = await getAuthHeaders()
26
+
27
+ if (!authHeaders) return null
28
+
29
+ const headers = {
30
+ ...authHeaders,
31
+ }
32
+
33
+ try {
34
+ const { customer } = await sdk.client.fetch<{ customer: HttpTypes.StoreCustomer }>(
35
+ `/store/customers/me`,
36
+ {
37
+ method: "GET",
38
+ query: { fields: "*addresses" },
39
+ headers: headers as Record<string, string>,
40
+ cache: "no-store",
41
+ }
42
+ )
43
+ return customer
44
+ } catch (error: any) {
45
+ const errStr = error.toString().toLowerCase()
46
+ // If the error indicates a pending deletion, return a special object
47
+ if (errStr.includes("active account deletion request") ||
48
+ (error.message && error.message.toLowerCase().includes("active account deletion request"))) {
49
+ const cookieStore = await cookies()
50
+ const cookieEmail = cookieStore.get("_medusa_customer_email")?.value
51
+ return { id: "pending_deletion", email: cookieEmail || "pending" } as any
52
+ }
53
+ return null
54
+ }
55
+ }
56
+ )
57
+
58
+ export const updateCustomer = async (body: HttpTypes.StoreUpdateCustomer) => {
59
+ const headers = {
60
+ ...(await getAuthHeaders()),
61
+ }
62
+
63
+ const updateRes = await sdk.store.customer
64
+ .update(body, {}, headers)
65
+ .then(({ customer }) => customer)
66
+ .catch(medusaError)
67
+
68
+ const cacheTag = await getCacheTag("customers")
69
+ revalidateTag(cacheTag)
70
+
71
+ return updateRes
72
+ }
73
+
74
+ export async function signup(_currentState: unknown, formData: FormData) {
75
+ const password = formData.get("password") as string
76
+ const customerForm = {
77
+ email: formData.get("email") as string,
78
+ first_name: formData.get("first_name") as string,
79
+ last_name: formData.get("last_name") as string,
80
+ phone: (formData.get("phone") as string)?.replace(/[^\d+]/g, ""),
81
+ }
82
+
83
+ try {
84
+ const token = await sdk.auth.register("customer", "emailpass", {
85
+ email: customerForm.email,
86
+ password: password,
87
+ })
88
+
89
+ await setAuthToken(token as string)
90
+
91
+ const headers = {
92
+ ...(await getAuthHeaders()),
93
+ }
94
+
95
+ const { customer: createdCustomer } = await sdk.store.customer.create(
96
+ customerForm,
97
+ {},
98
+ headers
99
+ )
100
+
101
+ const loginToken = await sdk.auth.login("customer", "emailpass", {
102
+ email: customerForm.email,
103
+ password,
104
+ })
105
+
106
+ await setAuthToken(loginToken as string)
107
+
108
+ const customerCacheTag = await getCacheTag("customers")
109
+ revalidateTag(customerCacheTag)
110
+
111
+ await transferCart(loginToken as string)
112
+
113
+ return createdCustomer
114
+ } catch (error: any) {
115
+ return error.toString()
116
+ }
117
+ }
118
+
119
+ export async function login(_currentState: unknown, formData: FormData) {
120
+ let email = formData.get("email_or_phone") as string
121
+ const password = formData.get("password") as string
122
+
123
+ // Sanitize phone number: remove spaces, dashes, etc if it looks like a phone number
124
+ // If it doesn't have '@', we treat it as a potential phone number and clean it
125
+ if (email && !email.includes("@")) {
126
+ email = email.replace(/[^\d+]/g, "")
127
+ }
128
+
129
+ let shouldRedirect = false
130
+ let countryCode = "in"
131
+
132
+ try {
133
+ const token = await sdk.auth.login("customer", "emailpass", { email_or_phone: email, password })
134
+ await setAuthToken(token as string)
135
+
136
+ // Store email in cookie for deletion flow (needed for cancellation modal)
137
+ const cookieStore = await cookies()
138
+ cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
139
+
140
+ // Force a check to see if we are blocked
141
+ try {
142
+ await sdk.store.customer.retrieve({}, {
143
+ authorization: `Bearer ${token}`
144
+ })
145
+ } catch (err: any) {
146
+ const errStr = err.toString().toLowerCase()
147
+ if (errStr.includes("active account deletion request")) {
148
+ return "ACCOUNT_DELETION_PENDING"
149
+ }
150
+ // If it's another error, we might still want to know, but deletion is our priority
151
+ throw err
152
+ }
153
+
154
+ // Transfer cart after login success - Moved here to ensure execution
155
+ try {
156
+ await transferCart(token as string)
157
+ } catch (e) {
158
+ console.error("Transfer cart failed in login:", e)
159
+ }
160
+
161
+ const customerCacheTag = await getCacheTag("customers")
162
+ revalidateTag(customerCacheTag)
163
+
164
+ countryCode = formData.get("country_code") as string || "in"
165
+ shouldRedirect = true
166
+ } catch (error: any) {
167
+ const errorStr = error.toString().toLowerCase()
168
+
169
+ if (errorStr.includes("active account deletion request") ||
170
+ (error.message && error.message.toLowerCase().includes("active account deletion request"))) {
171
+ // Still set email cookie even if blocked
172
+ const cookieStore = await cookies()
173
+ cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
174
+ return "ACCOUNT_DELETION_PENDING"
175
+ }
176
+
177
+ if (errorStr.includes("invalid") || errorStr.includes("401") || errorStr.includes("not found")) {
178
+ return "Invalid credentials. Please try again."
179
+ }
180
+ return error.message || error.toString()
181
+ }
182
+
183
+ if (shouldRedirect) {
184
+ const redirectUrl = formData.get("redirect_url") as string
185
+
186
+ // Check if it's a "keep-me-here" context (Wishlist or Review Popups)
187
+ const hasKeepContext = redirectUrl?.includes("login_context=keep")
188
+
189
+ // Security + Context check
190
+ if (redirectUrl && redirectUrl.startsWith("/") && hasKeepContext) {
191
+ redirect(redirectUrl)
192
+ } else {
193
+ // DEFAULT: Always Home Page
194
+ redirect(`/${countryCode}`)
195
+ }
196
+ }
197
+ }
198
+
199
+ export async function signout(countryCode: string) {
200
+ await sdk.auth.logout()
201
+
202
+ await removeAuthToken()
203
+ await removeCartId()
204
+ await removeSyncLock()
205
+
206
+ const customerCacheTag = await getCacheTag("customers")
207
+ revalidateTag(customerCacheTag)
208
+
209
+ await removeCartId()
210
+
211
+ const cartCacheTag = await getCacheTag("carts")
212
+ revalidateTag(cartCacheTag)
213
+
214
+ redirect(`/${countryCode}`)
215
+ }
216
+
217
+ export async function transferCart(token?: string) {
218
+ const cartId = await getCartId()
219
+ const authHeaders = token
220
+ ? { authorization: `Bearer ${token}` }
221
+ : await getAuthHeaders()
222
+
223
+ console.log("--- TransferCart Debug ---")
224
+ console.log("Token provided:", !!token)
225
+ console.log("Guest Cart ID:", cartId)
226
+
227
+ const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY
228
+ const backendUrl = process.env.MEDUSA_BACKEND_URL || process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL || "http://localhost:9000"
229
+
230
+ try {
231
+ console.log("Calling merge API (always)...")
232
+
233
+ const response = await fetch(`${backendUrl}/store/carts/merge`, {
234
+ method: "POST",
235
+ headers: {
236
+ "Content-Type": "application/json",
237
+ "x-publishable-api-key": publishableKey || "",
238
+ ...(authHeaders as any),
239
+ },
240
+ // Only include body if cartId exists
241
+ ...(cartId ? { body: JSON.stringify({ cart_id: cartId }) } : {}),
242
+ cache: "no-store",
243
+ })
244
+
245
+ if (response.ok) {
246
+ const data = await response.json()
247
+ console.log("--- Login Merge Response (JSON) ---")
248
+ console.log(JSON.stringify(data, null, 2))
249
+ console.log("-----------------------------------")
250
+
251
+ const activeCartId = data.cart?.id || data.id || data.cart_id
252
+
253
+ if (activeCartId) {
254
+ console.log("Storing Cart ID in cookie:", activeCartId)
255
+ await setCartId(activeCartId)
256
+ }
257
+ } else if (response.status === 404) {
258
+ // 404 is fine - it just means the customer has no active cart yet
259
+ console.log("No active cart found for this customer (expected).")
260
+ } else {
261
+ const errorText = await response.text()
262
+ console.log(`Merge API Info (${response.status}):`, errorText)
263
+ if (cartId) {
264
+ await sdk.store.cart.transferCart(cartId, {}, authHeaders).catch(() => { })
265
+ }
266
+ }
267
+ } catch (error: any) {
268
+ console.error("Merge API error:", error.message)
269
+ if (cartId) {
270
+ await sdk.store.cart.transferCart(cartId, {}, authHeaders).catch(() => { })
271
+ }
272
+ }
273
+
274
+ console.log("--------------------------")
275
+ const cartCacheTag = await getCacheTag("carts")
276
+ revalidateTag(cartCacheTag)
277
+ }
278
+
279
+ export const addCustomerAddress = async (
280
+ currentState: Record<string, unknown>,
281
+ formData: FormData
282
+ ): Promise<any> => {
283
+ const isDefaultBilling = (currentState.isDefaultBilling as boolean) || false
284
+ const isDefaultShipping = (currentState.isDefaultShipping as boolean) || false
285
+
286
+ const address: any = {
287
+ first_name: formData.get("first_name") as string,
288
+ last_name: formData.get("last_name") as string,
289
+ company: formData.get("company") as string,
290
+ address_1: formData.get("address_1") as string,
291
+ address_2: formData.get("address_2") as string,
292
+ city: formData.get("city") as string,
293
+ postal_code: formData.get("postal_code") as string,
294
+ province: formData.get("province") as string,
295
+ country_code: formData.get("country_code") as string,
296
+ phone: formData.get("phone") as string,
297
+ is_default_billing: isDefaultBilling,
298
+ is_default_shipping: isDefaultShipping,
299
+ metadata: {
300
+ address_type: (formData.get("address_type") as string) || "HOME",
301
+ }
302
+ }
303
+
304
+ const headers = {
305
+ ...(await getAuthHeaders()),
306
+ }
307
+
308
+ return sdk.store.customer
309
+ .createAddress(address, {}, headers)
310
+ .then(async ({ customer }) => {
311
+ const customerCacheTag = await getCacheTag("customers")
312
+ revalidateTag(customerCacheTag)
313
+ return { success: true, error: null }
314
+ })
315
+ .catch((err) => {
316
+ return { success: false, error: err.toString() }
317
+ })
318
+ }
319
+
320
+ export const deleteCustomerAddress = async (
321
+ addressId: string
322
+ ): Promise<void> => {
323
+ const headers = {
324
+ ...(await getAuthHeaders()),
325
+ }
326
+
327
+ await sdk.store.customer
328
+ .deleteAddress(addressId, headers)
329
+ .then(async () => {
330
+ const customerCacheTag = await getCacheTag("customers")
331
+ revalidateTag(customerCacheTag)
332
+ return { success: true, error: null }
333
+ })
334
+ .catch((err) => {
335
+ return { success: false, error: err.toString() }
336
+ })
337
+ }
338
+
339
+ export const updateCustomerAddress = async (
340
+ currentState: Record<string, unknown>,
341
+ formData: FormData
342
+ ): Promise<any> => {
343
+ const addressId =
344
+ (currentState.addressId as string) || (formData.get("addressId") as string)
345
+
346
+ if (!addressId) {
347
+ return { success: false, error: "Address ID is required" }
348
+ }
349
+
350
+ const address = {
351
+ first_name: formData.get("first_name") as string,
352
+ last_name: formData.get("last_name") as string,
353
+ company: formData.get("company") as string,
354
+ address_1: formData.get("address_1") as string,
355
+ address_2: formData.get("address_2") as string,
356
+ city: formData.get("city") as string,
357
+ postal_code: formData.get("postal_code") as string,
358
+ province: formData.get("province") as string,
359
+ country_code: formData.get("country_code") as string,
360
+ metadata: {
361
+ address_type: (formData.get("address_type") as string) || "HOME",
362
+ }
363
+ } as any
364
+
365
+ const phone = formData.get("phone") as string
366
+
367
+ if (phone) {
368
+ address.phone = phone
369
+ }
370
+
371
+ const headers = {
372
+ ...(await getAuthHeaders()),
373
+ }
374
+
375
+ return sdk.store.customer
376
+ .updateAddress(addressId, address, {}, headers)
377
+ .then(async () => {
378
+ const customerCacheTag = await getCacheTag("customers")
379
+ revalidateTag(customerCacheTag)
380
+ return { success: true, error: null }
381
+ })
382
+ .catch((err) => {
383
+ return { success: false, error: err.toString() }
384
+ })
385
+ }
386
+
387
+ export const setDefaultAddress = async (addressId: string) => {
388
+ const headers = {
389
+ ...(await getAuthHeaders()),
390
+ }
391
+
392
+ return sdk.store.customer
393
+ .updateAddress(addressId, { is_default_shipping: true }, {}, headers)
394
+ .then(async () => {
395
+ const customerCacheTag = await getCacheTag("customers")
396
+ revalidateTag(customerCacheTag)
397
+ return { success: true, error: null }
398
+ })
399
+ .catch((err) => {
400
+ return { success: false, error: err.toString() }
401
+ })
402
+ }
403
+
404
+ export async function initiateGoogleAuth() {
405
+ try {
406
+ const backendUrl = process.env.MEDUSA_BACKEND_URL || process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
407
+
408
+ if (!backendUrl) {
409
+ return { error: "Backend URL not configured" }
410
+ }
411
+
412
+ const response = await fetch(`${backendUrl}/auth/customer/google`, {
413
+ method: "POST",
414
+ headers: {
415
+ "Content-Type": "application/json",
416
+ },
417
+ cache: "no-store",
418
+ })
419
+
420
+ if (!response.ok) {
421
+ return { error: "Google auth failed" }
422
+ }
423
+
424
+ const result = await response.json()
425
+ let redirectUrl = typeof result === "string" ? result : (result as any)?.location
426
+
427
+ if (!redirectUrl) {
428
+ return { error: "No redirect URL received from Google OAuth" }
429
+ }
430
+
431
+ // Fix redirect URL: Remove country code prefix from callback URL if present
432
+ // Example: https://chocomelon.in/in/auth/customer/google/callback -> https://chocomelon.in/auth/customer/google/callback
433
+ try {
434
+ const url = new URL(redirectUrl)
435
+ const pathParts = url.pathname.split('/').filter(Boolean)
436
+
437
+ // Check if path starts with country code pattern (2-3 letter code)
438
+ if (pathParts.length > 0 && /^[a-z]{2,3}$/i.test(pathParts[0])) {
439
+ // Check if next part is 'auth'
440
+ if (pathParts.length > 1 && pathParts[1] === 'auth') {
441
+ // Remove country code from path
442
+ pathParts.shift()
443
+ url.pathname = '/' + pathParts.join('/')
444
+ redirectUrl = url.toString()
445
+ }
446
+ }
447
+ } catch (e) {
448
+ // If URL parsing fails, use original redirectUrl
449
+
450
+ }
451
+
452
+ return { redirectUrl }
453
+ } catch (error: any) {
454
+ return { error: error.message || "Internal server error" }
455
+ }
456
+ }
457
+
458
+ export async function handleGoogleAuthCallback(params: Record<string, string>) {
459
+ try {
460
+ if (!params.code || !params.state) {
461
+ return { error: "Missing authentication parameters" }
462
+ }
463
+
464
+ const backendUrl = process.env.MEDUSA_BACKEND_URL || process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
465
+
466
+ if (!backendUrl) {
467
+ return { error: "Backend URL not configured" }
468
+ }
469
+
470
+ const response = await fetch(
471
+ `${backendUrl}/auth/customer/google/callback?${new URLSearchParams(params).toString()}`,
472
+ {
473
+ method: "GET",
474
+ headers: {
475
+ "Content-Type": "application/json",
476
+ },
477
+ cache: "no-store",
478
+ }
479
+ )
480
+
481
+ if (!response.ok) {
482
+ return { error: `Authentication failed: ${response.status} - ${response.statusText}` }
483
+ }
484
+
485
+ const data = await response.json()
486
+ const token = data.token || data
487
+
488
+ if (!token || typeof token !== "string") {
489
+ return { error: "No token received" }
490
+ }
491
+
492
+ return { token }
493
+ } catch (error: any) {
494
+ return { error: error.message || "Internal server error" }
495
+ }
496
+ }
497
+
498
+ export async function handleGoogleCallback(token: string, email?: string, first_name?: string, last_name?: string) {
499
+ try {
500
+ if (!token || typeof token !== "string") {
501
+ return { error: "Token is required" }
502
+ }
503
+
504
+ await setAuthToken(token)
505
+ await transferCart(token).catch(() => { })
506
+
507
+ if (email && typeof email === "string") {
508
+ const cookieStore = await cookies()
509
+ cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
510
+
511
+ const headers = await getAuthHeaders()
512
+ if (headers && "authorization" in headers) {
513
+ try {
514
+ await sdk.store.customer.create(
515
+ { email, ...(first_name && { first_name }), ...(last_name && { last_name }) },
516
+ {},
517
+ headers
518
+ )
519
+ } catch (err: any) {
520
+ if (!err.message?.includes("already exists") && !err.message?.includes("duplicate")) {
521
+
522
+ }
523
+ }
524
+ }
525
+ }
526
+
527
+ const customerCacheTag = await getCacheTag("customers")
528
+ if (customerCacheTag) revalidateTag(customerCacheTag)
529
+
530
+ return { success: true }
531
+ } catch (error: any) {
532
+ return { error: error.message || "Internal server error" }
533
+ }
534
+ }
535
+
536
+ export async function uploadProfileImage(formData: FormData) {
537
+ try {
538
+ const imageFile = formData.get("image") as File
539
+
540
+ if (!imageFile) {
541
+ return { error: "No image file provided" }
542
+ }
543
+
544
+ // Create new FormData with 'files' field as expected by the backend
545
+ const uploadFormData = new FormData()
546
+ uploadFormData.append("files", imageFile)
547
+
548
+ const { uploadStoreFiles } = await import("medusa-reviews-logic/server")
549
+ const uploadResult = await uploadStoreFiles(uploadFormData)
550
+ if (!uploadResult.success) {
551
+ return { error: uploadResult.error }
552
+ }
553
+
554
+ const imageUrl = uploadResult.files?.[0]?.url
555
+
556
+ if (!imageUrl) {
557
+ throw new Error("No image URL returned from server")
558
+ }
559
+
560
+ // Fetch current customer to preserve existing metadata
561
+ const currentCustomer = await retrieveCustomer()
562
+ const existingMetadata = (currentCustomer?.metadata as Record<string, any>) || {}
563
+ const newMetadata = {
564
+ ...existingMetadata,
565
+ profile_image_url: imageUrl,
566
+ }
567
+
568
+ // Update customer metadata
569
+ await updateCustomer({ metadata: newMetadata })
570
+
571
+ const customerCacheTag = await getCacheTag("customers")
572
+ if (customerCacheTag) {
573
+ revalidateTag(customerCacheTag)
574
+ }
575
+
576
+ return { success: true, imageUrl }
577
+ } catch (error: any) {
578
+
579
+ return { error: error.message || "Failed to upload image" }
580
+ }
581
+ }