@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,43 @@
1
+ export function validateVariantId(variantId?: string): void {
2
+ if (!variantId) {
3
+ throw new Error("Missing variant ID when adding to cart")
4
+ }
5
+ }
6
+
7
+ export function validateLineId(lineId?: string): void {
8
+ if (!lineId) {
9
+ throw new Error("Missing lineItem ID when updating line item")
10
+ }
11
+ }
12
+
13
+ export function validateCartId(cartId?: string | null): void {
14
+ if (!cartId) {
15
+ throw new Error("Missing cart ID when updating line item")
16
+ }
17
+ }
18
+
19
+ export function validateQuantity(quantity: number): void {
20
+ if (quantity < 1) {
21
+ throw new Error("Quantity must be at least 1")
22
+ }
23
+ }
24
+
25
+ export function validateAddToCartInput(input: {
26
+ variantId: string
27
+ quantity: number
28
+ }): void {
29
+ validateVariantId(input.variantId)
30
+ validateQuantity(input.quantity)
31
+ }
32
+
33
+ export function validateLineItemUpdateInput(input: {
34
+ lineId: string
35
+ quantity: number
36
+ }): void {
37
+ validateLineId(input.lineId)
38
+ validateQuantity(input.quantity)
39
+ }
40
+
41
+ export function validateLineItemDeleteInput(lineId?: string): void {
42
+ validateLineId(lineId)
43
+ }
@@ -0,0 +1,49 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import { getProductPrice, getPricesForVariant } from "@core/util/get-product-price"
3
+
4
+ export type VariantPrice = NonNullable<ReturnType<typeof getPricesForVariant>>
5
+
6
+ export function getDisplayPrice(
7
+ product: HttpTypes.StoreProduct,
8
+ selectedVariant?: HttpTypes.StoreProductVariant
9
+ ): VariantPrice | null {
10
+ if (selectedVariant) {
11
+ const variantPrice = getPricesForVariant(selectedVariant)
12
+ if (variantPrice) {
13
+ return variantPrice
14
+ }
15
+ }
16
+
17
+ const { cheapestPrice } = getProductPrice({ product })
18
+ return cheapestPrice
19
+ }
20
+
21
+ export function getDiscountPercentage(
22
+ displayPrice: VariantPrice | null | undefined
23
+ ): number | null {
24
+ const originalPrice = displayPrice?.original_price_number
25
+ const currentPrice = displayPrice?.calculated_price_number
26
+
27
+ if (originalPrice && currentPrice && originalPrice > currentPrice) {
28
+ return Math.round(((originalPrice - currentPrice) / originalPrice) * 100)
29
+ }
30
+
31
+ return null
32
+ }
33
+
34
+ export function formatINRPrice(amount: number): string {
35
+ return `₹${amount.toLocaleString("en-IN")}`
36
+ }
37
+
38
+ export function formatDisplayPrice(
39
+ displayPrice: VariantPrice,
40
+ currentPrice?: number | null
41
+ ): string {
42
+ if (currentPrice !== undefined && currentPrice !== null) {
43
+ return formatINRPrice(currentPrice)
44
+ }
45
+
46
+ return displayPrice.calculated_price
47
+ }
48
+
49
+ export { getProductPrice, getPricesForVariant }
@@ -0,0 +1,193 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import { isEqual } from "lodash"
3
+
4
+ export type ProductOptions = Record<string, string | undefined>
5
+
6
+ export function optionsAsKeymap(
7
+ variantOptions: HttpTypes.StoreProductVariant["options"]
8
+ ): ProductOptions | undefined {
9
+ return variantOptions?.reduce((acc: Record<string, string>, varopt) => {
10
+ if (varopt.option_id) {
11
+ acc[varopt.option_id] = varopt.value
12
+ }
13
+ return acc
14
+ }, {})
15
+ }
16
+
17
+ export function getAutoSelectPartialOptions(
18
+ product: HttpTypes.StoreProduct
19
+ ): Record<string, string> {
20
+ const partialOptions: Record<string, string> = {}
21
+
22
+ product.options?.forEach((option) => {
23
+ const uniqueValues = new Set(option.values?.map((v) => v.value))
24
+
25
+ if (uniqueValues.size === 1) {
26
+ const uniqueValue = Array.from(uniqueValues)[0]
27
+ if (uniqueValue) {
28
+ partialOptions[option.id] = uniqueValue
29
+ }
30
+ }
31
+ })
32
+
33
+ return partialOptions
34
+ }
35
+
36
+ export function getInitialOptions(
37
+ product: HttpTypes.StoreProduct,
38
+ variantIdFromUrl?: string | null
39
+ ): ProductOptions {
40
+ if (variantIdFromUrl && product.variants) {
41
+ const variantFromUrl = product.variants.find((v) => v.id === variantIdFromUrl)
42
+ if (variantFromUrl) {
43
+ return optionsAsKeymap(variantFromUrl.options) ?? {}
44
+ }
45
+ }
46
+
47
+ if (product.variants?.length === 1) {
48
+ return optionsAsKeymap(product.variants[0].options) ?? {}
49
+ }
50
+
51
+ const partialOptions = getAutoSelectPartialOptions(product)
52
+ if (Object.keys(partialOptions).length > 0) {
53
+ return partialOptions
54
+ }
55
+
56
+ return {}
57
+ }
58
+
59
+ export function findVariantByOptions(
60
+ product: HttpTypes.StoreProduct,
61
+ options: ProductOptions
62
+ ): HttpTypes.StoreProductVariant | undefined {
63
+ if (!product.variants || product.variants.length === 0) {
64
+ return undefined
65
+ }
66
+
67
+ return product.variants.find((v) => {
68
+ const variantOptions = optionsAsKeymap(v.options)
69
+ return isEqual(variantOptions, options)
70
+ })
71
+ }
72
+
73
+ export function isValidVariantSelection(
74
+ product: HttpTypes.StoreProduct,
75
+ options: ProductOptions
76
+ ): boolean {
77
+ return (
78
+ product.variants?.some((v) => {
79
+ const variantOptions = optionsAsKeymap(v.options)
80
+ return isEqual(variantOptions, options)
81
+ }) ?? false
82
+ )
83
+ }
84
+
85
+ export function findColorOption(
86
+ product: HttpTypes.StoreProduct
87
+ ): HttpTypes.StoreProductOption | undefined {
88
+ return product.options?.find(
89
+ (opt) =>
90
+ opt.title?.toLowerCase().includes("color") ||
91
+ opt.title?.toLowerCase().includes("colour")
92
+ )
93
+ }
94
+
95
+ export function findSizeOption(
96
+ product: HttpTypes.StoreProduct
97
+ ): HttpTypes.StoreProductOption | undefined {
98
+ return product.options?.find((opt) => opt.title?.toLowerCase().includes("size"))
99
+ }
100
+
101
+ export function getUniqueColorVariants(
102
+ product: HttpTypes.StoreProduct,
103
+ colorOption: HttpTypes.StoreProductOption
104
+ ): HttpTypes.StoreProductVariant[] {
105
+ if (!product.variants) {
106
+ return []
107
+ }
108
+
109
+ const uniqueColors = new Map<string, HttpTypes.StoreProductVariant>()
110
+ product.variants.forEach((variant) => {
111
+ const colorValue = variant.options?.find(
112
+ (opt) => opt.option_id === colorOption.id
113
+ )?.value
114
+ if (colorValue && !uniqueColors.has(colorValue)) {
115
+ uniqueColors.set(colorValue, variant)
116
+ }
117
+ })
118
+
119
+ return Array.from(uniqueColors.values())
120
+ }
121
+
122
+ export function getOtherOptions(
123
+ product: HttpTypes.StoreProduct
124
+ ): HttpTypes.StoreProductOption[] {
125
+ return (product.options || []).filter((opt) => {
126
+ const isColor =
127
+ opt.title?.toLowerCase().includes("color") ||
128
+ opt.title?.toLowerCase().includes("colour")
129
+ const isSize = opt.title?.toLowerCase().includes("size")
130
+ return !isColor && !isSize
131
+ })
132
+ }
133
+
134
+ export function getMissingOptionIds(
135
+ product: HttpTypes.StoreProduct,
136
+ options: ProductOptions
137
+ ): Record<string, boolean> {
138
+ const missingOptions: Record<string, boolean> = {}
139
+ product.options?.forEach((option) => {
140
+ if (!options[option.id]) {
141
+ missingOptions[option.id] = true
142
+ }
143
+ })
144
+ return missingOptions
145
+ }
146
+
147
+ export function isVariantInStock(
148
+ variant: HttpTypes.StoreProductVariant | undefined,
149
+ product: HttpTypes.StoreProduct,
150
+ quantity: number,
151
+ quantityInCart: number
152
+ ): boolean {
153
+ if (!variant) {
154
+ return Boolean(product.variants && product.variants.length > 0)
155
+ }
156
+
157
+ if (!variant.manage_inventory) {
158
+ return true
159
+ }
160
+
161
+ if (variant.allow_backorder) {
162
+ return true
163
+ }
164
+
165
+ const totalInventory = variant.inventory_quantity || 0
166
+ const availableQuantity = totalInventory - quantityInCart
167
+
168
+ return availableQuantity >= quantity
169
+ }
170
+
171
+ export function getInventoryLimit(
172
+ variant: HttpTypes.StoreProductVariant | undefined,
173
+ quantityInCart: number
174
+ ): number | null {
175
+ if (!variant || !variant.manage_inventory || variant.allow_backorder) {
176
+ return null
177
+ }
178
+
179
+ const totalInventory = variant.inventory_quantity || 0
180
+ return totalInventory - quantityInCart
181
+ }
182
+
183
+ export function getVariantCartItem(
184
+ cart: HttpTypes.StoreCart | null | undefined,
185
+ variantId: string | undefined
186
+ ): HttpTypes.StoreCartLineItem | null {
187
+ if (!cart || !variantId) {
188
+ return null
189
+ }
190
+
191
+ const item = cart.items?.find((i) => i.variant_id === variantId)
192
+ return item || null
193
+ }
@@ -0,0 +1,48 @@
1
+ import { initializeApp, getApps, getApp } from "firebase/app"
2
+ import { getMessaging, getToken, onMessage } from "firebase/messaging"
3
+
4
+ const firebaseConfig = {
5
+ apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
6
+ authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
7
+ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
8
+ storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
9
+ messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
10
+ appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
11
+ }
12
+
13
+ const app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig)
14
+
15
+ export const requestNotificationPermission = async () => {
16
+ try {
17
+ if (typeof window === "undefined" || !("Notification" in window)) {
18
+ return null
19
+ }
20
+
21
+ const permission = await Notification.requestPermission()
22
+ if (permission === "granted") {
23
+ // Explicitly register service worker to avoid path issues
24
+ const registration = await navigator.serviceWorker.register(
25
+ "/firebase-messaging-sw.js"
26
+ )
27
+
28
+ const messaging = getMessaging(app)
29
+ const token = await getToken(messaging, {
30
+ vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY,
31
+ serviceWorkerRegistration: registration,
32
+ })
33
+ return token
34
+ }
35
+ } catch (error) {
36
+ throw error // Throwing so UI can catch it
37
+ }
38
+ }
39
+
40
+ export const onMessageListener = () =>
41
+ new Promise((resolve) => {
42
+ const messaging = getMessaging(app)
43
+ onMessage(messaging, (payload) => {
44
+ resolve(payload)
45
+ })
46
+ })
47
+
48
+ export default app
@@ -0,0 +1,8 @@
1
+ export { useAddToCart } from "./use-add-to-cart"
2
+ export { useProductReviews } from "./use-product-reviews"
3
+ export { useServerAction } from "./use-server-action"
4
+ export { useCart } from "./use-cart"
5
+ export { useProductVariant } from "./use-product-variant"
6
+ export { useProductActions } from "./use-product-actions"
7
+ export { useWishlist } from "./use-wishlist"
8
+ export { useCheckout } from "./use-checkout"
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import { useState, useCallback } from "react"
4
+ import { useParams } from "next/navigation"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { addToCart } from "@core/data/cart"
7
+ import { trackAddToCart } from "@core/analytics/ga4-ecommerce"
8
+
9
+ type UseAddToCartOptions = {
10
+ region?: HttpTypes.StoreRegion
11
+ }
12
+
13
+ export function useAddToCart({ region }: UseAddToCartOptions = {}) {
14
+ const { countryCode } = useParams() as { countryCode: string }
15
+ const [isAdding, setIsAdding] = useState<string | null>(null)
16
+
17
+ const handleAddToCart = useCallback(
18
+ async (product: HttpTypes.StoreProduct) => {
19
+ if (!product.variants || product.variants.length === 0) return
20
+
21
+ const firstVariant = product.variants[0]
22
+ if (!firstVariant.id) return
23
+
24
+ setIsAdding(product.id || null)
25
+ try {
26
+ await addToCart({
27
+ variantId: firstVariant.id,
28
+ quantity: 1,
29
+ countryCode,
30
+ })
31
+ const currency =
32
+ (
33
+ firstVariant as { calculated_price?: { currency_code?: string } }
34
+ ).calculated_price?.currency_code?.toUpperCase() ??
35
+ region?.currency_code ??
36
+ "USD"
37
+ const unitPrice =
38
+ (firstVariant as { calculated_price?: { calculated_amount?: number } })
39
+ .calculated_price?.calculated_amount ?? 0
40
+ trackAddToCart({
41
+ currency,
42
+ value: unitPrice / 100,
43
+ items: [
44
+ {
45
+ item_id: firstVariant.id,
46
+ item_name: product.title ?? "",
47
+ price: unitPrice / 100,
48
+ quantity: 1,
49
+ item_variant: firstVariant.title ?? undefined,
50
+ },
51
+ ],
52
+ })
53
+ } catch {
54
+ // Silence error — caller may handle UI feedback
55
+ } finally {
56
+ setIsAdding(null)
57
+ }
58
+ },
59
+ [countryCode, region]
60
+ )
61
+
62
+ return { isAdding, handleAddToCart }
63
+ }
@@ -0,0 +1,132 @@
1
+ "use client"
2
+
3
+ import { useCallback, useState } from "react"
4
+ import { useParams } from "next/navigation"
5
+ import {
6
+ addToCart,
7
+ updateLineItem,
8
+ deleteLineItem,
9
+ updateLineItemVariant,
10
+ } from "@core/data/cart"
11
+
12
+ type CartMutationResult = {
13
+ success: boolean
14
+ error?: string
15
+ }
16
+
17
+ export function useCart() {
18
+ const { countryCode } = useParams() as { countryCode: string }
19
+ const [loadingLineId, setLoadingLineId] = useState<string | null>(null)
20
+ const [isAdding, setIsAdding] = useState(false)
21
+ const [error, setError] = useState<string | null>(null)
22
+
23
+ const addItem = useCallback(
24
+ async (variantId: string, quantity = 1): Promise<CartMutationResult> => {
25
+ setError(null)
26
+ setIsAdding(true)
27
+
28
+ try {
29
+ await addToCart({ variantId, quantity, countryCode })
30
+ return { success: true }
31
+ } catch (err) {
32
+ const message =
33
+ err instanceof Error ? err.message : "Failed to add item to cart"
34
+ setError(message)
35
+ return { success: false, error: message }
36
+ } finally {
37
+ setIsAdding(false)
38
+ }
39
+ },
40
+ [countryCode]
41
+ )
42
+
43
+ const updateItem = useCallback(
44
+ async (lineId: string, quantity: number): Promise<CartMutationResult> => {
45
+ setError(null)
46
+ setLoadingLineId(lineId)
47
+
48
+ try {
49
+ await updateLineItem({ lineId, quantity })
50
+ return { success: true }
51
+ } catch (err) {
52
+ const message =
53
+ err instanceof Error ? err.message : "Failed to update cart item"
54
+ setError(message)
55
+ return { success: false, error: message }
56
+ } finally {
57
+ setLoadingLineId(null)
58
+ }
59
+ },
60
+ []
61
+ )
62
+
63
+ const removeItem = useCallback(
64
+ async (lineId: string): Promise<CartMutationResult> => {
65
+ setError(null)
66
+ setLoadingLineId(lineId)
67
+
68
+ try {
69
+ await deleteLineItem(lineId)
70
+ return { success: true }
71
+ } catch (err) {
72
+ const message =
73
+ err instanceof Error ? err.message : "Failed to remove cart item"
74
+ setError(message)
75
+ return { success: false, error: message }
76
+ } finally {
77
+ setLoadingLineId(null)
78
+ }
79
+ },
80
+ []
81
+ )
82
+
83
+ const updateItemVariant = useCallback(
84
+ async (
85
+ lineId: string,
86
+ variantId: string,
87
+ quantity: number
88
+ ): Promise<CartMutationResult> => {
89
+ setError(null)
90
+ setLoadingLineId(lineId)
91
+
92
+ try {
93
+ await updateLineItemVariant({
94
+ lineId,
95
+ variantId,
96
+ quantity,
97
+ countryCode,
98
+ })
99
+ return { success: true }
100
+ } catch (err) {
101
+ const message =
102
+ err instanceof Error ? err.message : "Failed to update cart item variant"
103
+ setError(message)
104
+ return { success: false, error: message }
105
+ } finally {
106
+ setLoadingLineId(null)
107
+ }
108
+ },
109
+ [countryCode]
110
+ )
111
+
112
+ const clearError = useCallback(() => {
113
+ setError(null)
114
+ }, [])
115
+
116
+ const isLineLoading = useCallback(
117
+ (lineId: string) => loadingLineId === lineId,
118
+ [loadingLineId]
119
+ )
120
+
121
+ return {
122
+ addItem,
123
+ updateItem,
124
+ removeItem,
125
+ updateItemVariant,
126
+ isAdding,
127
+ loadingLineId,
128
+ isLineLoading,
129
+ error,
130
+ clearError,
131
+ }
132
+ }
@@ -0,0 +1,62 @@
1
+ "use client"
2
+
3
+ import { useCallback, useState } from "react"
4
+ import { useParams, useRouter } from "next/navigation"
5
+ import {
6
+ setAddresses,
7
+ placeOrder,
8
+ setShippingMethod,
9
+ initiatePaymentSession,
10
+ } from "@core/data/cart"
11
+ import { HttpTypes } from "@medusajs/types"
12
+ import { useServerAction } from "./use-server-action"
13
+
14
+ type UseCheckoutOptions = {
15
+ cart: HttpTypes.StoreCart
16
+ }
17
+
18
+ export function useCheckout({ cart }: UseCheckoutOptions) {
19
+ const router = useRouter()
20
+ const { countryCode } = useParams() as { countryCode: string }
21
+ const [isPlacingOrder, setIsPlacingOrder] = useState(false)
22
+
23
+ const addressAction = useServerAction((formData: FormData) =>
24
+ setAddresses(null, formData)
25
+ )
26
+
27
+ const handleSetShipping = useCallback(
28
+ async (shippingMethodId: string) => {
29
+ await setShippingMethod({ cartId: cart.id, shippingMethodId })
30
+ router.refresh()
31
+ },
32
+ [cart.id, router]
33
+ )
34
+
35
+ const handleInitiatePayment = useCallback(
36
+ async (data: HttpTypes.StoreInitializePaymentSession) => {
37
+ return initiatePaymentSession(cart, data)
38
+ },
39
+ [cart]
40
+ )
41
+
42
+ const handlePlaceOrder = useCallback(
43
+ async (cartId?: string) => {
44
+ setIsPlacingOrder(true)
45
+ try {
46
+ await placeOrder(cartId ?? cart.id)
47
+ } finally {
48
+ setIsPlacingOrder(false)
49
+ }
50
+ },
51
+ [cart.id]
52
+ )
53
+
54
+ return {
55
+ countryCode,
56
+ isPlacingOrder,
57
+ addressAction,
58
+ handleSetShipping,
59
+ handleInitiatePayment,
60
+ handlePlaceOrder,
61
+ }
62
+ }
@@ -0,0 +1,29 @@
1
+ import { RefObject, useEffect, useState } from "react"
2
+
3
+ export const useIntersection = (
4
+ element: RefObject<HTMLDivElement | null>,
5
+ rootMargin: string
6
+ ) => {
7
+ const [isVisible, setState] = useState(false)
8
+
9
+ useEffect(() => {
10
+ if (!element.current) {
11
+ return
12
+ }
13
+
14
+ const el = element.current
15
+
16
+ const observer = new IntersectionObserver(
17
+ ([entry]) => {
18
+ setState(entry.isIntersecting)
19
+ },
20
+ { rootMargin }
21
+ )
22
+
23
+ observer.observe(el)
24
+
25
+ return () => observer.unobserve(el)
26
+ }, [element, rootMargin])
27
+
28
+ return isVisible
29
+ }