@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
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @pradip1995/commerce-core
|
|
2
|
+
|
|
3
|
+
Medusa storefront commerce logic: server actions, domain rules, hooks, types.
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { sdk } from "@pradip1995/commerce-core/config"
|
|
9
|
+
import { useAddToCart } from "@pradip1995/commerce-core/hooks/use-add-to-cart"
|
|
10
|
+
import type { HomePageData } from "@pradip1995/commerce-core/types/home"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Peer dependencies
|
|
14
|
+
|
|
15
|
+
`next`, `react`, `@medusajs/js-sdk`, `@medusajs/types`
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pradip1995/commerce-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Medusa storefront commerce logic — data, domain, hooks, types, utils",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/SmartByteLabs/medusa-storefront-kit.git",
|
|
13
|
+
"directory": "packages/commerce-core"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"exports": {
|
|
20
|
+
"./config": "./src/config.ts",
|
|
21
|
+
"./constants": "./src/constants.tsx",
|
|
22
|
+
"./firebase": "./src/firebase.ts",
|
|
23
|
+
"./analytics/ga4-ecommerce": "./src/analytics/ga4-ecommerce.ts",
|
|
24
|
+
"./context/*": "./src/context/*.tsx",
|
|
25
|
+
"./data/*": "./src/data/*.ts",
|
|
26
|
+
"./data/cart": "./src/data/cart/index.ts",
|
|
27
|
+
"./domain/cart": "./src/domain/cart/index.ts",
|
|
28
|
+
"./domain/product/*": "./src/domain/product/*.ts",
|
|
29
|
+
"./hooks": "./src/hooks/index.ts",
|
|
30
|
+
"./hooks/*": "./src/hooks/*",
|
|
31
|
+
"./types/*": "./src/types/*.ts",
|
|
32
|
+
"./util/*": "./src/util/*.ts",
|
|
33
|
+
"./theme/inline-vars": "./src/theme/inline-vars.ts"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@medusajs/js-sdk": ">=2",
|
|
37
|
+
"@medusajs/types": ">=2",
|
|
38
|
+
"next": ">=15",
|
|
39
|
+
"react": ">=19",
|
|
40
|
+
"react-dom": ">=19"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"country-state-city": "^3.2.1",
|
|
44
|
+
"firebase": "^12.8.0",
|
|
45
|
+
"lodash": "^4.17.21",
|
|
46
|
+
"pg": "^8.11.3",
|
|
47
|
+
"qs": "^6.12.1",
|
|
48
|
+
"server-only": "^0.0.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@medusajs/icons": "latest",
|
|
52
|
+
"@medusajs/js-sdk": "latest",
|
|
53
|
+
"@medusajs/types": "latest",
|
|
54
|
+
"@types/lodash": "^4.17.0",
|
|
55
|
+
"@types/node": "^20",
|
|
56
|
+
"@types/react": "^19",
|
|
57
|
+
"@types/react-dom": "^19",
|
|
58
|
+
"color": "^5.0.3",
|
|
59
|
+
"eslint": "^8.57.0",
|
|
60
|
+
"next": "15.3.8",
|
|
61
|
+
"react": "19.0.3",
|
|
62
|
+
"react-dom": "19.0.3",
|
|
63
|
+
"typescript": "^5.7.2"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
67
|
+
"typecheck:full": "tsc --noEmit",
|
|
68
|
+
"lint": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GA4 E-commerce event helpers for dataLayer.
|
|
3
|
+
* Use these to power GA4 "Drive sales" reports: Ecommerce purchases,
|
|
4
|
+
* Purchase journey, Checkout journey, Transactions, Promotions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type GA4Item = {
|
|
8
|
+
item_id: string
|
|
9
|
+
item_name: string
|
|
10
|
+
price?: number
|
|
11
|
+
quantity?: number
|
|
12
|
+
index?: number
|
|
13
|
+
item_variant?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pushToDataLayer(obj: Record<string, unknown>) {
|
|
17
|
+
if (typeof window === "undefined") return
|
|
18
|
+
const w = window as Window & { dataLayer?: unknown[] }
|
|
19
|
+
w.dataLayer = w.dataLayer || []
|
|
20
|
+
w.dataLayer.push(obj)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function trackAddToCart(payload: {
|
|
24
|
+
currency: string
|
|
25
|
+
value: number
|
|
26
|
+
items: GA4Item[]
|
|
27
|
+
}) {
|
|
28
|
+
pushToDataLayer({ event: "add_to_cart", ecommerce: payload })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function trackRemoveFromCart(payload: {
|
|
32
|
+
currency: string
|
|
33
|
+
value: number
|
|
34
|
+
items: GA4Item[]
|
|
35
|
+
}) {
|
|
36
|
+
pushToDataLayer({ event: "remove_from_cart", ecommerce: payload })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function trackViewCart(payload: {
|
|
40
|
+
currency: string
|
|
41
|
+
value: number
|
|
42
|
+
items: GA4Item[]
|
|
43
|
+
}) {
|
|
44
|
+
pushToDataLayer({ event: "view_cart", ecommerce: payload })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function trackViewItem(payload: {
|
|
48
|
+
currency: string
|
|
49
|
+
value: number
|
|
50
|
+
items: GA4Item[]
|
|
51
|
+
}) {
|
|
52
|
+
pushToDataLayer({ event: "view_item", ecommerce: payload })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function trackViewItemList(payload: {
|
|
56
|
+
list_id?: string
|
|
57
|
+
list_name?: string
|
|
58
|
+
items: GA4Item[]
|
|
59
|
+
}) {
|
|
60
|
+
pushToDataLayer({ event: "view_item_list", ecommerce: payload })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function trackBeginCheckout(payload: {
|
|
64
|
+
currency: string
|
|
65
|
+
value: number
|
|
66
|
+
items: GA4Item[]
|
|
67
|
+
}) {
|
|
68
|
+
pushToDataLayer({ event: "begin_checkout", ecommerce: payload })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function trackAddShippingInfo(payload: {
|
|
72
|
+
currency: string
|
|
73
|
+
value: number
|
|
74
|
+
items: GA4Item[]
|
|
75
|
+
}) {
|
|
76
|
+
pushToDataLayer({ event: "add_shipping_info", ecommerce: payload })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function trackAddPaymentInfo(payload: {
|
|
80
|
+
currency: string
|
|
81
|
+
value: number
|
|
82
|
+
items: GA4Item[]
|
|
83
|
+
}) {
|
|
84
|
+
pushToDataLayer({ event: "add_payment_info", ecommerce: payload })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function trackPurchase(payload: {
|
|
88
|
+
transaction_id: string
|
|
89
|
+
currency: string
|
|
90
|
+
value: number
|
|
91
|
+
tax?: number
|
|
92
|
+
shipping?: number
|
|
93
|
+
items: GA4Item[]
|
|
94
|
+
}) {
|
|
95
|
+
pushToDataLayer({ event: "purchase", ecommerce: payload })
|
|
96
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getLocaleHeader } from "@core/util/get-locale-header"
|
|
2
|
+
import Medusa, { FetchArgs, FetchInput } from "@medusajs/js-sdk"
|
|
3
|
+
|
|
4
|
+
// Defaults to standard port for Medusa server
|
|
5
|
+
let MEDUSA_BACKEND_URL = "http://localhost:9000"
|
|
6
|
+
|
|
7
|
+
if (process.env.MEDUSA_BACKEND_URL) {
|
|
8
|
+
MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const sdk = new Medusa({
|
|
12
|
+
baseUrl: MEDUSA_BACKEND_URL,
|
|
13
|
+
debug: process.env.NODE_ENV === "development",
|
|
14
|
+
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const originalFetch = sdk.client.fetch.bind(sdk.client)
|
|
18
|
+
|
|
19
|
+
sdk.client.fetch = async <T>(input: FetchInput, init?: FetchArgs): Promise<T> => {
|
|
20
|
+
const headers = init?.headers ?? {}
|
|
21
|
+
let localeHeader: Record<string, string | null> | undefined
|
|
22
|
+
try {
|
|
23
|
+
localeHeader = await getLocaleHeader()
|
|
24
|
+
headers["x-medusa-locale"] ??= localeHeader["x-medusa-locale"]
|
|
25
|
+
} catch {}
|
|
26
|
+
|
|
27
|
+
const newHeaders = {
|
|
28
|
+
...localeHeader,
|
|
29
|
+
...headers,
|
|
30
|
+
}
|
|
31
|
+
init = {
|
|
32
|
+
...init,
|
|
33
|
+
headers: newHeaders,
|
|
34
|
+
}
|
|
35
|
+
return originalFetch(input, init)
|
|
36
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { CreditCard } from "@medusajs/icons"
|
|
3
|
+
|
|
4
|
+
import Ideal from "@modules/common/icons/ideal"
|
|
5
|
+
import Bancontact from "@modules/common/icons/bancontact"
|
|
6
|
+
import PayPal from "@modules/common/icons/paypal"
|
|
7
|
+
|
|
8
|
+
/* Map of payment provider_id to their title and icon. Add in any payment providers you want to use. */
|
|
9
|
+
export const paymentInfoMap: Record<
|
|
10
|
+
string,
|
|
11
|
+
{ title: string; icon: React.JSX.Element }
|
|
12
|
+
> = {
|
|
13
|
+
pp_stripe_stripe: {
|
|
14
|
+
title: "Credit card",
|
|
15
|
+
icon: <CreditCard />,
|
|
16
|
+
},
|
|
17
|
+
"pp_medusa-payments_default": {
|
|
18
|
+
title: "Credit card",
|
|
19
|
+
icon: <CreditCard />,
|
|
20
|
+
},
|
|
21
|
+
"pp_stripe-ideal_stripe": {
|
|
22
|
+
title: "iDeal",
|
|
23
|
+
icon: <Ideal />,
|
|
24
|
+
},
|
|
25
|
+
"pp_stripe-bancontact_stripe": {
|
|
26
|
+
title: "Bancontact",
|
|
27
|
+
icon: <Bancontact />,
|
|
28
|
+
},
|
|
29
|
+
pp_paypal_paypal: {
|
|
30
|
+
title: "PayPal",
|
|
31
|
+
icon: <PayPal />,
|
|
32
|
+
},
|
|
33
|
+
pp_system_default: {
|
|
34
|
+
title: "Cash on Delivery (Cash/UPI)",
|
|
35
|
+
icon: <CreditCard />,
|
|
36
|
+
},
|
|
37
|
+
razorpay: {
|
|
38
|
+
title: "Razorpay",
|
|
39
|
+
icon: <CreditCard />,
|
|
40
|
+
},
|
|
41
|
+
pp_razorpay_razorpay: {
|
|
42
|
+
title: "Razorpay",
|
|
43
|
+
icon: <CreditCard />,
|
|
44
|
+
},
|
|
45
|
+
// Add more payment providers here
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// This only checks if it is native stripe or medusa payments for card payments, it ignores the other stripe-based providers
|
|
49
|
+
export const isStripeLike = (providerId?: string) => {
|
|
50
|
+
return providerId?.startsWith("pp_stripe_") || providerId?.startsWith("pp_medusa-")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const isPaypal = (providerId?: string) => {
|
|
54
|
+
return providerId?.startsWith("pp_paypal")
|
|
55
|
+
}
|
|
56
|
+
export const isManual = (providerId?: string) => {
|
|
57
|
+
return providerId?.startsWith("pp_system_default")
|
|
58
|
+
}
|
|
59
|
+
export const isRazorpay = (providerId?: string) => {
|
|
60
|
+
return providerId?.startsWith("razorpay") || providerId?.startsWith("pp_razorpay")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add currencies that don't need to be divided by 100
|
|
64
|
+
export const noDivisionCurrencies = [
|
|
65
|
+
"krw",
|
|
66
|
+
"jpy",
|
|
67
|
+
"vnd",
|
|
68
|
+
"clp",
|
|
69
|
+
"pyg",
|
|
70
|
+
"xaf",
|
|
71
|
+
"xof",
|
|
72
|
+
"bif",
|
|
73
|
+
"djf",
|
|
74
|
+
"gnf",
|
|
75
|
+
"kmf",
|
|
76
|
+
"mga",
|
|
77
|
+
"rwf",
|
|
78
|
+
"xpf",
|
|
79
|
+
"htg",
|
|
80
|
+
"vuv",
|
|
81
|
+
"xag",
|
|
82
|
+
"xdr",
|
|
83
|
+
"xau",
|
|
84
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useState } from "react"
|
|
4
|
+
|
|
5
|
+
interface ModalContextType {
|
|
6
|
+
isModalOpen: boolean
|
|
7
|
+
setIsModalOpen: (isOpen: boolean) => void
|
|
8
|
+
close: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ModalContext = createContext<ModalContextType | undefined>(undefined)
|
|
12
|
+
|
|
13
|
+
export const ModalProvider: React.FC<{
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
close?: () => void
|
|
16
|
+
}> = ({ children, close: closeProp }) => {
|
|
17
|
+
const [isModalOpen, setIsModalOpen] = useState(false)
|
|
18
|
+
|
|
19
|
+
const close = () => {
|
|
20
|
+
if (closeProp) {
|
|
21
|
+
closeProp()
|
|
22
|
+
} else {
|
|
23
|
+
setIsModalOpen(false)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ModalContext.Provider value={{ isModalOpen, setIsModalOpen, close }}>
|
|
29
|
+
{children}
|
|
30
|
+
</ModalContext.Provider>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const useModal = () => {
|
|
35
|
+
const context = useContext(ModalContext)
|
|
36
|
+
if (context === undefined) {
|
|
37
|
+
throw new Error("useModal must be used within a ModalProvider")
|
|
38
|
+
}
|
|
39
|
+
return context
|
|
40
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
8
|
+
useCallback,
|
|
9
|
+
} from "react"
|
|
10
|
+
import {
|
|
11
|
+
getWishlistProductIds,
|
|
12
|
+
addToWishlist as addToWishlistApi,
|
|
13
|
+
removeFromWishlist as removeFromWishlistApi,
|
|
14
|
+
} from "@core/data/wishlist"
|
|
15
|
+
|
|
16
|
+
interface WishlistContextType {
|
|
17
|
+
wishlistIds: string[]
|
|
18
|
+
count: number
|
|
19
|
+
isWishlisted: (productId: string) => boolean
|
|
20
|
+
toggleWishlist: (productId: string) => Promise<{ success: boolean; error?: string }>
|
|
21
|
+
refreshWishlist: () => Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const WishlistContext = createContext<WishlistContextType | undefined>(undefined)
|
|
25
|
+
|
|
26
|
+
export const WishlistProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
27
|
+
children,
|
|
28
|
+
}) => {
|
|
29
|
+
const [wishlistIds, setWishlistIds] = useState<string[]>([])
|
|
30
|
+
|
|
31
|
+
const refreshWishlist = useCallback(async () => {
|
|
32
|
+
try {
|
|
33
|
+
const ids = await getWishlistProductIds()
|
|
34
|
+
setWishlistIds(ids)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Error silently ignored for production
|
|
37
|
+
}
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
refreshWishlist()
|
|
42
|
+
}, [refreshWishlist])
|
|
43
|
+
|
|
44
|
+
const isWishlisted = useCallback(
|
|
45
|
+
(productId: string) => {
|
|
46
|
+
return wishlistIds.includes(productId)
|
|
47
|
+
},
|
|
48
|
+
[wishlistIds]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const toggleWishlist = async (productId: string) => {
|
|
52
|
+
const currentlyWishlisted = isWishlisted(productId)
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (currentlyWishlisted) {
|
|
56
|
+
const result = await removeFromWishlistApi(productId)
|
|
57
|
+
if (result.success) {
|
|
58
|
+
setWishlistIds((prev) => prev.filter((id) => id !== productId))
|
|
59
|
+
return { success: true }
|
|
60
|
+
}
|
|
61
|
+
return { success: false, error: result.error }
|
|
62
|
+
} else {
|
|
63
|
+
const result = await addToWishlistApi(productId)
|
|
64
|
+
if (result.success) {
|
|
65
|
+
setWishlistIds((prev) => [...prev, productId])
|
|
66
|
+
return { success: true }
|
|
67
|
+
}
|
|
68
|
+
return { success: false, error: result.error }
|
|
69
|
+
}
|
|
70
|
+
} catch (error: any) {
|
|
71
|
+
return { success: false, error: error.message || "An error occurred" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<WishlistContext.Provider
|
|
77
|
+
value={{
|
|
78
|
+
wishlistIds,
|
|
79
|
+
count: wishlistIds.length,
|
|
80
|
+
isWishlisted,
|
|
81
|
+
toggleWishlist,
|
|
82
|
+
refreshWishlist,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</WishlistContext.Provider>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const useWishlist = () => {
|
|
91
|
+
const context = useContext(WishlistContext)
|
|
92
|
+
if (context === undefined) {
|
|
93
|
+
throw new Error("useWishlist must be used within a WishlistProvider")
|
|
94
|
+
}
|
|
95
|
+
return context
|
|
96
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { sdk } from "@core/config"
|
|
4
|
+
import medusaError from "@core/util/medusa-error"
|
|
5
|
+
import { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import { revalidateTag, revalidatePath } from "next/cache"
|
|
7
|
+
import { redirect } from "next/navigation"
|
|
8
|
+
import { headers as getRequestHeaders } from "next/headers"
|
|
9
|
+
import {
|
|
10
|
+
getAuthHeaders,
|
|
11
|
+
getCacheOptions,
|
|
12
|
+
getCacheTag,
|
|
13
|
+
getCartId,
|
|
14
|
+
removeCartId,
|
|
15
|
+
setCartId,
|
|
16
|
+
getHoldCartId,
|
|
17
|
+
setHoldCartId,
|
|
18
|
+
removeHoldCartId,
|
|
19
|
+
setBuyNowCartId,
|
|
20
|
+
removeBuyNowCartId,
|
|
21
|
+
} from "../cookies"
|
|
22
|
+
import { getRegion } from "../regions"
|
|
23
|
+
import { getLocale } from "@core/data/locale-actions"
|
|
24
|
+
import { retrieveCustomer, transferCart } from "@core/data/customer"
|
|
25
|
+
import { cache } from "react"
|
|
26
|
+
import {
|
|
27
|
+
createBuyNowMetadata,
|
|
28
|
+
filterOutCurrentCart,
|
|
29
|
+
getBuyNowCheckoutUrl,
|
|
30
|
+
getPostOrderCookieAction,
|
|
31
|
+
getResumeAbandonedCheckoutUrl,
|
|
32
|
+
isEphemeralCart,
|
|
33
|
+
normalizePhoneForRazorpay,
|
|
34
|
+
partitionAbandonedCarts,
|
|
35
|
+
shouldCheckEphemeralCartMetadata,
|
|
36
|
+
shouldSkipToPayment,
|
|
37
|
+
validateAddToCartInput,
|
|
38
|
+
validateCartId,
|
|
39
|
+
validateLineItemDeleteInput,
|
|
40
|
+
validateLineItemUpdateInput,
|
|
41
|
+
validateRazorpayPreflight,
|
|
42
|
+
} from "@core/domain/cart"
|
|
43
|
+
|
|
44
|
+
export async function getAbandonedCarts() {
|
|
45
|
+
const headers = {
|
|
46
|
+
...(await getAuthHeaders()),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If no authorization, return empty arrays
|
|
50
|
+
if (!headers["authorization"]) {
|
|
51
|
+
return { buyNowCarts: [], reorderCarts: [] }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Just fetch all carts for the customer and filter locally
|
|
56
|
+
const res = await sdk.client
|
|
57
|
+
.fetch<{ carts: HttpTypes.StoreCart[] }>(`/store/carts`, {
|
|
58
|
+
method: "GET",
|
|
59
|
+
query: {
|
|
60
|
+
fields:
|
|
61
|
+
"*items, *items.product, *items.product.thumbnail, *items.variant, *items.metadata, +total",
|
|
62
|
+
},
|
|
63
|
+
headers,
|
|
64
|
+
cache: "no-store",
|
|
65
|
+
})
|
|
66
|
+
.catch(() => null)
|
|
67
|
+
|
|
68
|
+
let buyNowCarts: HttpTypes.StoreCart[] = []
|
|
69
|
+
let reorderCarts: HttpTypes.StoreCart[] = []
|
|
70
|
+
|
|
71
|
+
if (res?.carts) {
|
|
72
|
+
const currentCartId = await getCartId()
|
|
73
|
+
const filteredCarts = filterOutCurrentCart(res.carts, currentCartId)
|
|
74
|
+
const partitioned = partitionAbandonedCarts(filteredCarts)
|
|
75
|
+
|
|
76
|
+
buyNowCarts = partitioned.buyNowCarts
|
|
77
|
+
reorderCarts = partitioned.reorderCarts
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { buyNowCarts, reorderCarts }
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error("Failed to fetch abandoned carts:", e)
|
|
83
|
+
return { buyNowCarts: [], reorderCarts: [] }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function resumeAbandonedCart(cartId: string, countryCode: string) {
|
|
88
|
+
// Set as buy now cart so it temporarily overrides the main cart
|
|
89
|
+
// without deleting the main cart cookie (_medusa_cart_id)
|
|
90
|
+
await setBuyNowCartId(cartId)
|
|
91
|
+
|
|
92
|
+
redirect(getResumeAbandonedCheckoutUrl(countryCode, cartId))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function deleteCart(cartId: string) {
|
|
96
|
+
const headers = await getAuthHeaders()
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await sdk.client.fetch(`/store/carts/${cartId}`, {
|
|
100
|
+
method: "DELETE",
|
|
101
|
+
headers,
|
|
102
|
+
cache: "no-store",
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
revalidateTag("carts")
|
|
106
|
+
return { success: true }
|
|
107
|
+
} catch (e: any) {
|
|
108
|
+
console.error("Failed to delete cart:", e)
|
|
109
|
+
return { success: false, error: e.message }
|
|
110
|
+
}
|
|
111
|
+
}
|