medusa-storefront-data 1.0.0 → 2.1.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 (110) hide show
  1. package/dist/cookies.d.ts +2 -2
  2. package/dist/cookies.d.ts.map +1 -1
  3. package/dist/edge.d.ts +3 -0
  4. package/dist/edge.d.ts.map +1 -0
  5. package/dist/edge.js +1 -0
  6. package/dist/middleware.d.ts +3 -0
  7. package/dist/middleware.d.ts.map +1 -0
  8. package/dist/middleware.js +1 -0
  9. package/dist/server/cart.d.ts +9 -5
  10. package/dist/server/cart.d.ts.map +1 -1
  11. package/dist/server/cart.js +164 -194
  12. package/dist/server/categories.d.ts +3 -2
  13. package/dist/server/categories.d.ts.map +1 -1
  14. package/dist/server/categories.js +14 -51
  15. package/dist/server/collections.d.ts.map +1 -1
  16. package/dist/server/collections.js +16 -61
  17. package/dist/server/contact.d.ts +34 -0
  18. package/dist/server/contact.d.ts.map +1 -0
  19. package/dist/server/contact.js +57 -0
  20. package/dist/server/customer.d.ts +7 -7
  21. package/dist/server/customer.d.ts.map +1 -1
  22. package/dist/server/customer.js +96 -145
  23. package/dist/server/dynamic-config.d.ts.map +1 -1
  24. package/dist/server/dynamic-config.js +38 -12
  25. package/dist/server/fulfillment.d.ts +4 -3
  26. package/dist/server/fulfillment.d.ts.map +1 -1
  27. package/dist/server/fulfillment.js +16 -41
  28. package/dist/server/guest.d.ts +35 -63
  29. package/dist/server/guest.d.ts.map +1 -1
  30. package/dist/server/guest.js +81 -202
  31. package/dist/server/home.d.ts +15 -0
  32. package/dist/server/home.d.ts.map +1 -0
  33. package/dist/server/home.js +45 -0
  34. package/dist/server/index.d.ts +2 -0
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +2 -0
  37. package/dist/server/locale-actions.d.ts +1 -1
  38. package/dist/server/locale-actions.d.ts.map +1 -1
  39. package/dist/server/locale-actions.js +8 -13
  40. package/dist/server/locales.d.ts +2 -4
  41. package/dist/server/locales.d.ts.map +1 -1
  42. package/dist/server/locales.js +5 -13
  43. package/dist/server/orders.d.ts +5 -11
  44. package/dist/server/orders.d.ts.map +1 -1
  45. package/dist/server/orders.js +126 -267
  46. package/dist/server/payment-details.d.ts +4 -4
  47. package/dist/server/payment-details.d.ts.map +1 -1
  48. package/dist/server/payment-details.js +17 -42
  49. package/dist/server/payment.d.ts +2 -1
  50. package/dist/server/payment.d.ts.map +1 -1
  51. package/dist/server/payment.js +9 -21
  52. package/dist/server/pincode.d.ts +7 -0
  53. package/dist/server/pincode.d.ts.map +1 -0
  54. package/dist/server/pincode.js +30 -0
  55. package/dist/server/products.d.ts +15 -19
  56. package/dist/server/products.d.ts.map +1 -1
  57. package/dist/server/products.js +47 -178
  58. package/dist/server/regions.d.ts +1 -1
  59. package/dist/server/regions.d.ts.map +1 -1
  60. package/dist/server/regions.js +6 -3
  61. package/dist/server/returns.d.ts +4 -4
  62. package/dist/server/returns.d.ts.map +1 -1
  63. package/dist/server/returns.js +50 -154
  64. package/dist/server/swaps.d.ts +7 -6
  65. package/dist/server/swaps.d.ts.map +1 -1
  66. package/dist/server/swaps.js +23 -57
  67. package/dist/server/variants.d.ts.map +1 -1
  68. package/dist/server/variants.js +11 -22
  69. package/dist/server/wishlist.d.ts +11 -0
  70. package/dist/server/wishlist.d.ts.map +1 -0
  71. package/dist/server/wishlist.js +49 -0
  72. package/dist/util/get-locale-header.d.ts +1 -1
  73. package/dist/util/revalidate-cart.d.ts +2 -0
  74. package/dist/util/revalidate-cart.d.ts.map +1 -0
  75. package/dist/util/revalidate-cart.js +8 -0
  76. package/dist/util/sort-products.d.ts +3 -0
  77. package/dist/util/sort-products.d.ts.map +1 -0
  78. package/dist/util/sort-products.js +1 -0
  79. package/dist/util/store-client.d.ts +13 -0
  80. package/dist/util/store-client.d.ts.map +1 -0
  81. package/dist/util/store-client.js +77 -0
  82. package/package.json +95 -37
  83. package/src/edge.ts +2 -0
  84. package/src/middleware.ts +2 -2
  85. package/src/server/cart.ts +214 -267
  86. package/src/server/categories.ts +19 -72
  87. package/src/server/collections.ts +25 -82
  88. package/src/server/contact.ts +92 -0
  89. package/src/server/customer.ts +146 -190
  90. package/src/server/dynamic-config.ts +38 -12
  91. package/src/server/fulfillment.ts +27 -53
  92. package/src/server/guest.ts +159 -276
  93. package/src/server/home.ts +68 -0
  94. package/src/server/index.ts +1 -0
  95. package/src/server/locale-actions.ts +8 -15
  96. package/src/server/locales.ts +6 -18
  97. package/src/server/orders.ts +167 -337
  98. package/src/server/payment-details.ts +24 -52
  99. package/src/server/payment.ts +8 -28
  100. package/src/server/pincode.ts +49 -0
  101. package/src/server/products.ts +72 -235
  102. package/src/server/regions.ts +10 -6
  103. package/src/server/returns.ts +75 -189
  104. package/src/server/swaps.ts +94 -123
  105. package/src/server/variants.ts +9 -28
  106. package/src/server/wishlist.ts +1 -1
  107. package/src/util/revalidate-cart.ts +10 -0
  108. package/src/util/sort-products.ts +2 -47
  109. package/src/util/store-client.ts +93 -0
  110. package/src/services/middleware.ts +0 -54
@@ -1,6 +1,5 @@
1
1
  "use server"
2
2
 
3
- import { sdk } from "../config"
4
3
  import medusaError from "../util/medusa-error"
5
4
  import { HttpTypes } from "@medusajs/types"
6
5
  import { revalidateTag } from "next/cache"
@@ -8,7 +7,6 @@ import { redirect } from "next/navigation"
8
7
  import { cookies } from "next/headers"
9
8
  import {
10
9
  getAuthHeaders,
11
- getCacheOptions,
12
10
  getCacheTag,
13
11
  getCartId,
14
12
  removeAuthToken,
@@ -17,35 +15,48 @@ import {
17
15
  setAuthToken,
18
16
  setCartId,
19
17
  } from "../cookies"
18
+ import {
19
+ medusaAuthLogin,
20
+ medusaAuthLogout,
21
+ medusaAuthRegister,
22
+ } from "medusa-services/auth"
23
+ import {
24
+ isAccountDeletionPendingError,
25
+ medusaCustomerCreate,
26
+ medusaCustomerCreateAddress,
27
+ medusaCustomerDeleteAddress,
28
+ medusaCustomerRetrieve,
29
+ medusaCustomerUpdate,
30
+ medusaCustomerUpdateAddress,
31
+ } from "medusa-services/customer"
32
+ import { medusaCartMerge, medusaCartTransfer } from "medusa-services/cart"
33
+ import {
34
+ getAuthClientOptions,
35
+ getMedusaBackendUrl,
36
+ getStoreCartClientOptions,
37
+ } from "../util/store-client"
20
38
 
21
39
  import { cache } from "react"
22
40
 
41
+ function throwCustomerError(error: unknown): never {
42
+ if (error && typeof error === "object" && "response" in error) {
43
+ medusaError(error)
44
+ }
45
+ throw error instanceof Error ? error : new Error(String(error))
46
+ }
47
+
23
48
  export const retrieveCustomer = cache(
24
49
  async (): Promise<HttpTypes.StoreCustomer | null> => {
25
50
  const authHeaders = await getAuthHeaders()
26
51
 
27
- if (!authHeaders) return null
28
-
29
- const headers = {
30
- ...authHeaders,
31
- }
52
+ if (!("authorization" in authHeaders)) return null
32
53
 
33
54
  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"))) {
55
+ const options = await getStoreCartClientOptions()
56
+ const { customer } = await medusaCustomerRetrieve(options, { fields: "*addresses" })
57
+ return customer as unknown as HttpTypes.StoreCustomer
58
+ } catch (error: unknown) {
59
+ if (isAccountDeletionPendingError(error)) {
49
60
  const cookieStore = await cookies()
50
61
  const cookieEmail = cookieStore.get("_medusa_customer_email")?.value
51
62
  return { id: "pending_deletion", email: cookieEmail || "pending" } as any
@@ -56,19 +67,17 @@ export const retrieveCustomer = cache(
56
67
  )
57
68
 
58
69
  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)
70
+ try {
71
+ const options = await getStoreCartClientOptions()
72
+ const { customer } = await medusaCustomerUpdate(body as Record<string, unknown>, options)
67
73
 
68
- const cacheTag = await getCacheTag("customers")
69
- revalidateTag(cacheTag)
74
+ const cacheTag = await getCacheTag("customers")
75
+ revalidateTag(cacheTag)
70
76
 
71
- return updateRes
77
+ return customer as unknown as HttpTypes.StoreCustomer
78
+ } catch (e) {
79
+ throwCustomerError(e)
80
+ }
72
81
  }
73
82
 
74
83
  export async function signup(_currentState: unknown, formData: FormData) {
@@ -81,34 +90,28 @@ export async function signup(_currentState: unknown, formData: FormData) {
81
90
  }
82
91
 
83
92
  try {
84
- const token = await sdk.auth.register("customer", "emailpass", {
85
- email: customerForm.email,
86
- password: password,
87
- })
93
+ const authOptions = await getAuthClientOptions()
94
+ const token = await medusaAuthRegister(
95
+ { email: customerForm.email, password },
96
+ authOptions
97
+ )
88
98
 
89
- await setAuthToken(token as string)
99
+ await setAuthToken(token)
90
100
 
91
- const headers = {
92
- ...(await getAuthHeaders()),
93
- }
101
+ const options = await getStoreCartClientOptions()
102
+ const { customer: createdCustomer } = await medusaCustomerCreate(customerForm, options)
94
103
 
95
- const { customer: createdCustomer } = await sdk.store.customer.create(
96
- customerForm,
97
- {},
98
- headers
104
+ const loginToken = await medusaAuthLogin(
105
+ { email: customerForm.email, password },
106
+ authOptions
99
107
  )
100
108
 
101
- const loginToken = await sdk.auth.login("customer", "emailpass", {
102
- email: customerForm.email,
103
- password,
104
- })
105
-
106
- await setAuthToken(loginToken as string)
109
+ await setAuthToken(loginToken)
107
110
 
108
111
  const customerCacheTag = await getCacheTag("customers")
109
112
  revalidateTag(customerCacheTag)
110
113
 
111
- await transferCart(loginToken as string)
114
+ await transferCart(loginToken)
112
115
 
113
116
  return createdCustomer
114
117
  } catch (error: any) {
@@ -116,12 +119,13 @@ export async function signup(_currentState: unknown, formData: FormData) {
116
119
  }
117
120
  }
118
121
 
119
- export async function login(_currentState: unknown, formData: FormData) {
122
+ export async function login(
123
+ _currentState: string | null,
124
+ formData: FormData
125
+ ): Promise<string | null> {
120
126
  let email = formData.get("email_or_phone") as string
121
127
  const password = formData.get("password") as string
122
128
 
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
129
  if (email && !email.includes("@")) {
126
130
  email = email.replace(/[^\d+]/g, "")
127
131
  }
@@ -130,30 +134,28 @@ export async function login(_currentState: unknown, formData: FormData) {
130
134
  let countryCode = "in"
131
135
 
132
136
  try {
133
- const token = await sdk.auth.login("customer", "emailpass", { email_or_phone: email, password })
134
- await setAuthToken(token as string)
137
+ const authOptions = await getAuthClientOptions()
138
+ const token = await medusaAuthLogin({ email_or_phone: email, password }, authOptions)
139
+ await setAuthToken(token)
135
140
 
136
- // Store email in cookie for deletion flow (needed for cancellation modal)
137
141
  const cookieStore = await cookies()
138
142
  cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
139
143
 
140
- // Force a check to see if we are blocked
141
144
  try {
142
- await sdk.store.customer.retrieve({}, {
143
- authorization: `Bearer ${token}`
145
+ const options = await getStoreCartClientOptions()
146
+ await medusaCustomerRetrieve({
147
+ ...options,
148
+ authorization: `Bearer ${token}`,
144
149
  })
145
- } catch (err: any) {
146
- const errStr = err.toString().toLowerCase()
147
- if (errStr.includes("active account deletion request")) {
150
+ } catch (err: unknown) {
151
+ if (isAccountDeletionPendingError(err)) {
148
152
  return "ACCOUNT_DELETION_PENDING"
149
153
  }
150
- // If it's another error, we might still want to know, but deletion is our priority
151
154
  throw err
152
155
  }
153
156
 
154
- // Transfer cart after login success - Moved here to ensure execution
155
157
  try {
156
- await transferCart(token as string)
158
+ await transferCart(token)
157
159
  } catch (e) {
158
160
  console.error("Transfer cart failed in login:", e)
159
161
  }
@@ -161,43 +163,44 @@ export async function login(_currentState: unknown, formData: FormData) {
161
163
  const customerCacheTag = await getCacheTag("customers")
162
164
  revalidateTag(customerCacheTag)
163
165
 
164
- countryCode = formData.get("country_code") as string || "in"
166
+ countryCode = (formData.get("country_code") as string) || "in"
165
167
  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
168
+ } catch (error: unknown) {
169
+ if (isAccountDeletionPendingError(error)) {
172
170
  const cookieStore = await cookies()
173
171
  cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
174
172
  return "ACCOUNT_DELETION_PENDING"
175
173
  }
176
174
 
175
+ const errorStr = String(error).toLowerCase()
177
176
  if (errorStr.includes("invalid") || errorStr.includes("401") || errorStr.includes("not found")) {
178
177
  return "Invalid credentials. Please try again."
179
178
  }
180
- return error.message || error.toString()
179
+
180
+ return error instanceof Error ? error.message : String(error)
181
181
  }
182
182
 
183
183
  if (shouldRedirect) {
184
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
185
  const hasKeepContext = redirectUrl?.includes("login_context=keep")
188
186
 
189
- // Security + Context check
190
187
  if (redirectUrl && redirectUrl.startsWith("/") && hasKeepContext) {
191
188
  redirect(redirectUrl)
192
189
  } else {
193
- // DEFAULT: Always Home Page
194
190
  redirect(`/${countryCode}`)
195
191
  }
196
192
  }
193
+
194
+ return null
197
195
  }
198
196
 
199
197
  export async function signout(countryCode: string) {
200
- await sdk.auth.logout()
198
+ try {
199
+ const authOptions = await getAuthClientOptions()
200
+ await medusaAuthLogout(authOptions)
201
+ } catch {
202
+ // Continue local cleanup even if remote logout fails
203
+ }
201
204
 
202
205
  await removeAuthToken()
203
206
  await removeCartId()
@@ -216,58 +219,38 @@ export async function signout(countryCode: string) {
216
219
 
217
220
  export async function transferCart(token?: string) {
218
221
  const cartId = await getCartId()
219
- const authHeaders = token
220
- ? { authorization: `Bearer ${token}` }
221
- : await getAuthHeaders()
222
+ const options = await getStoreCartClientOptions()
223
+ const mergeOptions = token
224
+ ? { ...options, authorization: `Bearer ${token}` }
225
+ : options
222
226
 
223
227
  console.log("--- TransferCart Debug ---")
224
228
  console.log("Token provided:", !!token)
225
229
  console.log("Guest Cart ID:", cartId)
226
230
 
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
231
  try {
231
232
  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
233
 
245
- if (response.ok) {
246
- const data = await response.json()
234
+ const data = await medusaCartMerge(mergeOptions, cartId ?? undefined)
235
+
236
+ if (data) {
247
237
  console.log("--- Login Merge Response (JSON) ---")
248
238
  console.log(JSON.stringify(data, null, 2))
249
239
  console.log("-----------------------------------")
250
-
240
+
251
241
  const activeCartId = data.cart?.id || data.id || data.cart_id
252
-
253
- if (activeCartId) {
242
+
243
+ if (activeCartId && typeof activeCartId === "string") {
254
244
  console.log("Storing Cart ID in cookie:", activeCartId)
255
245
  await setCartId(activeCartId)
256
246
  }
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
247
  } 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
- }
248
+ console.log("No active cart found for this customer (expected).")
266
249
  }
267
- } catch (error: any) {
268
- console.error("Merge API error:", error.message)
250
+ } catch (error: unknown) {
251
+ console.error("Merge API error:", error instanceof Error ? error.message : error)
269
252
  if (cartId) {
270
- await sdk.store.cart.transferCart(cartId, {}, authHeaders).catch(() => { })
253
+ await medusaCartTransfer(cartId, mergeOptions).catch(() => {})
271
254
  }
272
255
  }
273
256
 
@@ -298,42 +281,34 @@ export const addCustomerAddress = async (
298
281
  is_default_shipping: isDefaultShipping,
299
282
  metadata: {
300
283
  address_type: (formData.get("address_type") as string) || "HOME",
301
- }
284
+ },
302
285
  }
303
286
 
304
- const headers = {
305
- ...(await getAuthHeaders()),
306
- }
287
+ try {
288
+ const options = await getStoreCartClientOptions()
289
+ await medusaCustomerCreateAddress(address, options)
307
290
 
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
- })
291
+ const customerCacheTag = await getCacheTag("customers")
292
+ revalidateTag(customerCacheTag)
293
+ return { success: true, error: null }
294
+ } catch (err) {
295
+ return { success: false, error: String(err) }
296
+ }
318
297
  }
319
298
 
320
299
  export const deleteCustomerAddress = async (
321
300
  addressId: string
322
301
  ): Promise<void> => {
323
- const headers = {
324
- ...(await getAuthHeaders()),
325
- }
302
+ try {
303
+ const options = await getStoreCartClientOptions()
304
+ await medusaCustomerDeleteAddress(addressId, options)
326
305
 
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
- })
306
+ const customerCacheTag = await getCacheTag("customers")
307
+ revalidateTag(customerCacheTag)
308
+ return { success: true, error: null } as any
309
+ } catch (err) {
310
+ return { success: false, error: String(err) } as any
311
+ }
337
312
  }
338
313
 
339
314
  export const updateCustomerAddress = async (
@@ -359,7 +334,7 @@ export const updateCustomerAddress = async (
359
334
  country_code: formData.get("country_code") as string,
360
335
  metadata: {
361
336
  address_type: (formData.get("address_type") as string) || "HOME",
362
- }
337
+ },
363
338
  } as any
364
339
 
365
340
  const phone = formData.get("phone") as string
@@ -368,42 +343,34 @@ export const updateCustomerAddress = async (
368
343
  address.phone = phone
369
344
  }
370
345
 
371
- const headers = {
372
- ...(await getAuthHeaders()),
373
- }
346
+ try {
347
+ const options = await getStoreCartClientOptions()
348
+ await medusaCustomerUpdateAddress(addressId, address, options)
374
349
 
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
- })
350
+ const customerCacheTag = await getCacheTag("customers")
351
+ revalidateTag(customerCacheTag)
352
+ return { success: true, error: null }
353
+ } catch (err) {
354
+ return { success: false, error: String(err) }
355
+ }
385
356
  }
386
357
 
387
358
  export const setDefaultAddress = async (addressId: string) => {
388
- const headers = {
389
- ...(await getAuthHeaders()),
390
- }
359
+ try {
360
+ const options = await getStoreCartClientOptions()
361
+ await medusaCustomerUpdateAddress(addressId, { is_default_shipping: true }, options)
391
362
 
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
- })
363
+ const customerCacheTag = await getCacheTag("customers")
364
+ revalidateTag(customerCacheTag)
365
+ return { success: true, error: null }
366
+ } catch (err) {
367
+ return { success: false, error: String(err) }
368
+ }
402
369
  }
403
370
 
404
371
  export async function initiateGoogleAuth() {
405
372
  try {
406
- const backendUrl = process.env.MEDUSA_BACKEND_URL || process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
373
+ const backendUrl = getMedusaBackendUrl()
407
374
 
408
375
  if (!backendUrl) {
409
376
  return { error: "Backend URL not configured" }
@@ -428,25 +395,19 @@ export async function initiateGoogleAuth() {
428
395
  return { error: "No redirect URL received from Google OAuth" }
429
396
  }
430
397
 
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
398
  try {
434
399
  const url = new URL(redirectUrl)
435
- const pathParts = url.pathname.split('/').filter(Boolean)
400
+ const pathParts = url.pathname.split("/").filter(Boolean)
436
401
 
437
- // Check if path starts with country code pattern (2-3 letter code)
438
402
  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
403
+ if (pathParts.length > 1 && pathParts[1] === "auth") {
442
404
  pathParts.shift()
443
- url.pathname = '/' + pathParts.join('/')
405
+ url.pathname = "/" + pathParts.join("/")
444
406
  redirectUrl = url.toString()
445
407
  }
446
408
  }
447
409
  } catch (e) {
448
410
  // If URL parsing fails, use original redirectUrl
449
-
450
411
  }
451
412
 
452
413
  return { redirectUrl }
@@ -461,7 +422,7 @@ export async function handleGoogleAuthCallback(params: Record<string, string>) {
461
422
  return { error: "Missing authentication parameters" }
462
423
  }
463
424
 
464
- const backendUrl = process.env.MEDUSA_BACKEND_URL || process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
425
+ const backendUrl = getMedusaBackendUrl()
465
426
 
466
427
  if (!backendUrl) {
467
428
  return { error: "Backend URL not configured" }
@@ -502,23 +463,22 @@ export async function handleGoogleCallback(token: string, email?: string, first_
502
463
  }
503
464
 
504
465
  await setAuthToken(token)
505
- await transferCart(token).catch(() => { })
466
+ await transferCart(token).catch(() => {})
506
467
 
507
468
  if (email && typeof email === "string") {
508
469
  const cookieStore = await cookies()
509
470
  cookieStore.set("_medusa_customer_email", email, { maxAge: 60 * 60 * 24 * 7, path: "/" })
510
471
 
511
- const headers = await getAuthHeaders()
512
- if (headers && "authorization" in headers) {
472
+ const options = await getStoreCartClientOptions()
473
+ if (options.authorization) {
513
474
  try {
514
- await sdk.store.customer.create(
475
+ await medusaCustomerCreate(
515
476
  { email, ...(first_name && { first_name }), ...(last_name && { last_name }) },
516
- {},
517
- headers
477
+ options
518
478
  )
519
479
  } catch (err: any) {
520
480
  if (!err.message?.includes("already exists") && !err.message?.includes("duplicate")) {
521
-
481
+ // Ignore duplicate customer errors for OAuth sign-in
522
482
  }
523
483
  }
524
484
  }
@@ -541,7 +501,6 @@ export async function uploadProfileImage(formData: FormData) {
541
501
  return { error: "No image file provided" }
542
502
  }
543
503
 
544
- // Create new FormData with 'files' field as expected by the backend
545
504
  const uploadFormData = new FormData()
546
505
  uploadFormData.append("files", imageFile)
547
506
 
@@ -557,7 +516,6 @@ export async function uploadProfileImage(formData: FormData) {
557
516
  throw new Error("No image URL returned from server")
558
517
  }
559
518
 
560
- // Fetch current customer to preserve existing metadata
561
519
  const currentCustomer = await retrieveCustomer()
562
520
  const existingMetadata = (currentCustomer?.metadata as Record<string, any>) || {}
563
521
  const newMetadata = {
@@ -565,7 +523,6 @@ export async function uploadProfileImage(formData: FormData) {
565
523
  profile_image_url: imageUrl,
566
524
  }
567
525
 
568
- // Update customer metadata
569
526
  await updateCustomer({ metadata: newMetadata })
570
527
 
571
528
  const customerCacheTag = await getCacheTag("customers")
@@ -575,7 +532,6 @@ export async function uploadProfileImage(formData: FormData) {
575
532
 
576
533
  return { success: true, imageUrl }
577
534
  } catch (error: any) {
578
-
579
535
  return { error: error.message || "Failed to upload image" }
580
536
  }
581
537
  }
@@ -186,10 +186,10 @@ export const getHomeBannersFromConfig = async (): Promise<Array<{
186
186
  const banner = item["homepage-banner"]
187
187
  return {
188
188
  image: banner?.["homepage-banner-image"],
189
- title: banner?.["homepage-banner-title"],
190
- subtitle: banner?.["homepage-banner-subtitle"],
191
- description: banner?.["homepage-banner-description"],
192
- buttonName: banner?.["homepage-banner-button-name"],
189
+ title: normalizeCmsText(banner?.["homepage-banner-title"]),
190
+ subtitle: normalizeCmsText(banner?.["homepage-banner-subtitle"]),
191
+ description: normalizeCmsText(banner?.["homepage-banner-description"]),
192
+ buttonName: normalizeBannerButtonName(banner?.["homepage-banner-button-name"]),
193
193
  buttonLink: banner?.["homepage-banner-button-link"],
194
194
  }
195
195
  })
@@ -218,10 +218,10 @@ export const getAppBannersFromConfig = async (): Promise<Array<{
218
218
  const banner = item["app-banner"]
219
219
  return {
220
220
  image: banner?.["app-banner-image"],
221
- title: banner?.["app-banner-title"],
222
- subtitle: banner?.["app-banner-subtitle"],
223
- description: banner?.["app-banner-description"],
224
- buttonName: banner?.["app-banner-button-name"],
221
+ title: normalizeCmsText(banner?.["app-banner-title"]),
222
+ subtitle: normalizeCmsText(banner?.["app-banner-subtitle"]),
223
+ description: normalizeCmsText(banner?.["app-banner-description"]),
224
+ buttonName: normalizeBannerButtonName(banner?.["app-banner-button-name"]),
225
225
  buttonLink: banner?.["app-banner-button-link"],
226
226
  }
227
227
  })
@@ -260,7 +260,7 @@ export const getFeaturesFromConfig = async (): Promise<{ title: string; features
260
260
 
261
261
  if (!homepageConfig) return null
262
262
 
263
- const title = homepageConfig["why-choose-us-title"] || "Why Choose Chocomelon?"
263
+ const title = homepageConfig["why-choose-us-title"] || "Why Choose Us?"
264
264
  const featuresRaw = homepageConfig["why-choose-us-features"] || []
265
265
 
266
266
  const features = featuresRaw.map((item: any) => {
@@ -279,6 +279,32 @@ export const getFeaturesFromConfig = async (): Promise<{ title: string; features
279
279
  }
280
280
  }
281
281
 
282
+ const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
283
+
284
+ /** Fix common UTF-8 mojibake from CMS copy-paste (e.g. em dash, apostrophe). */
285
+ function normalizeCmsText(value: unknown): string | undefined {
286
+ if (typeof value !== "string" || !value.trim()) return undefined
287
+ return value
288
+ .trim()
289
+ .replace(/ΓÇö/g, "—")
290
+ .replace(/ΓÇÖ/g, "'")
291
+ .replace(/ΓÇ£|ΓÇ¥/g, '"')
292
+ .replace(/≡ƒÿÄ≡ƒæòΓ£¿/g, " 👕✨")
293
+ }
294
+
295
+ function normalizeBannerButtonName(value: unknown): string | undefined {
296
+ const text = normalizeCmsText(value)
297
+ if (!text) return undefined
298
+ if (/^shop\s+now\s*1$/i.test(text)) return "Shop Now"
299
+ return text
300
+ }
301
+
302
+ function normalizeConfigEmail(value: unknown): string | undefined {
303
+ if (typeof value !== "string") return undefined
304
+ const trimmed = value.trim()
305
+ return EMAIL_PATTERN.test(trimmed) ? trimmed : undefined
306
+ }
307
+
282
308
  export const getContactInfoFromConfig = async (): Promise<{ phone?: string; email?: string; address?: string } | null> => {
283
309
  try {
284
310
  const config = await getDynamicConfig()
@@ -293,7 +319,7 @@ export const getContactInfoFromConfig = async (): Promise<{ phone?: string; emai
293
319
 
294
320
  return {
295
321
  phone: contactConfig["contact-phone"],
296
- email: contactConfig["contact-email"],
322
+ email: normalizeConfigEmail(contactConfig["contact-email"]),
297
323
  address: contactConfig["contact-address"],
298
324
  }
299
325
  } catch (error) {
@@ -392,10 +418,10 @@ export const getPromoBarConfig = async (): Promise<{ text: string | null; code:
392
418
  const promoConfig = config?.["homepage-config"]?.["promo-bar"]
393
419
 
394
420
  return {
395
- text: promoConfig?.["promo-text"] || null,
421
+ text: normalizeCmsText(promoConfig?.["promo-text"]) || null,
396
422
  code: promoConfig?.["promo-code"] || null,
397
423
  value: promoConfig?.["promo-value"] || null,
398
- active: promoConfig?.["promo-active"] ?? false
424
+ active: promoConfig?.["promo-active"] ?? false,
399
425
  }
400
426
  } catch (error) {
401
427
  return { text: null, code: null, value: null, active: false }