@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,190 @@
1
+ "use client"
2
+
3
+ import { useCallback, useMemo, useState } from "react"
4
+ import { useParams } from "next/navigation"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import { addToCart, buyNow, updateLineItem, deleteLineItem } from "@core/data/cart"
7
+ import { trackAddToCart } from "@core/analytics/ga4-ecommerce"
8
+ import {
9
+ type ProductOptions,
10
+ isVariantInStock,
11
+ getInventoryLimit,
12
+ getVariantCartItem,
13
+ } from "@core/domain/product/variant-selection"
14
+ import { type VariantPrice } from "@core/domain/product/pricing"
15
+
16
+ type UseProductActionsOptions = {
17
+ product: HttpTypes.StoreProduct
18
+ region?: HttpTypes.StoreRegion
19
+ cart?: HttpTypes.StoreCart | null
20
+ selectedVariant?: HttpTypes.StoreProductVariant
21
+ options: ProductOptions
22
+ isValidVariant: boolean
23
+ displayPrice: VariantPrice | null
24
+ validateOptions: () => boolean
25
+ }
26
+
27
+ export function useProductActions({
28
+ product,
29
+ region,
30
+ cart,
31
+ selectedVariant,
32
+ options,
33
+ isValidVariant,
34
+ displayPrice,
35
+ validateOptions,
36
+ }: UseProductActionsOptions) {
37
+ const { countryCode } = useParams() as { countryCode: string }
38
+ const [isAdding, setIsAdding] = useState(false)
39
+ const [isBuyingNow, setIsBuyingNow] = useState(false)
40
+ const [quantity, setQuantity] = useState(1)
41
+
42
+ const variantInCart = useMemo(
43
+ () => getVariantCartItem(cart, selectedVariant?.id),
44
+ [cart, selectedVariant?.id]
45
+ )
46
+
47
+ const quantityInCart = useMemo(() => variantInCart?.quantity || 0, [variantInCart])
48
+
49
+ const inStock = useMemo(
50
+ () => isVariantInStock(selectedVariant, product, quantity, quantityInCart),
51
+ [selectedVariant, product, quantity, quantityInCart]
52
+ )
53
+
54
+ const inventoryLimit = useMemo(
55
+ () => getInventoryLimit(selectedVariant, quantityInCart),
56
+ [selectedVariant, quantityInCart]
57
+ )
58
+
59
+ const trackCartAdd = useCallback(() => {
60
+ if (!selectedVariant?.id) {
61
+ return
62
+ }
63
+
64
+ const currency = region?.currency_code?.toUpperCase() ?? "USD"
65
+ const unitPrice = displayPrice?.calculated_price_number ?? 0
66
+ const value = (unitPrice * quantity) / 100
67
+
68
+ trackAddToCart({
69
+ currency,
70
+ value,
71
+ items: [
72
+ {
73
+ item_id: selectedVariant.id,
74
+ item_name: product.title ?? "",
75
+ price: unitPrice / 100,
76
+ quantity,
77
+ item_variant: selectedVariant.title ?? undefined,
78
+ },
79
+ ],
80
+ })
81
+ }, [selectedVariant, region, displayPrice, quantity, product.title])
82
+
83
+ const handleAddToCart = useCallback(async () => {
84
+ if (!validateOptions()) {
85
+ return null
86
+ }
87
+
88
+ if (!selectedVariant?.id) {
89
+ return null
90
+ }
91
+
92
+ setIsAdding(true)
93
+
94
+ try {
95
+ await addToCart({
96
+ variantId: selectedVariant.id,
97
+ quantity,
98
+ countryCode,
99
+ })
100
+ trackCartAdd()
101
+ } finally {
102
+ setIsAdding(false)
103
+ }
104
+ }, [validateOptions, selectedVariant, quantity, countryCode, trackCartAdd])
105
+
106
+ const handleBuyNow = useCallback(async () => {
107
+ if (!validateOptions()) {
108
+ return null
109
+ }
110
+
111
+ if (!selectedVariant?.id) {
112
+ return null
113
+ }
114
+
115
+ setIsBuyingNow(true)
116
+
117
+ try {
118
+ await buyNow({
119
+ variantId: selectedVariant.id,
120
+ quantity,
121
+ countryCode,
122
+ })
123
+ } catch (e) {
124
+ console.error(e)
125
+ } finally {
126
+ setIsBuyingNow(false)
127
+ }
128
+ }, [validateOptions, selectedVariant, quantity, countryCode])
129
+
130
+ const handleIncreaseQuantity = useCallback(async () => {
131
+ if (!variantInCart || isAdding) {
132
+ return
133
+ }
134
+
135
+ setIsAdding(true)
136
+ try {
137
+ await updateLineItem({
138
+ lineId: variantInCart.id,
139
+ quantity: variantInCart.quantity + 1,
140
+ })
141
+ } catch (e) {
142
+ console.error(e)
143
+ } finally {
144
+ setIsAdding(false)
145
+ }
146
+ }, [variantInCart, isAdding])
147
+
148
+ const handleDecreaseQuantity = useCallback(async () => {
149
+ if (!variantInCart || isAdding) {
150
+ return
151
+ }
152
+
153
+ setIsAdding(true)
154
+ try {
155
+ if (variantInCart.quantity > 1) {
156
+ await updateLineItem({
157
+ lineId: variantInCart.id,
158
+ quantity: variantInCart.quantity - 1,
159
+ })
160
+ } else {
161
+ await deleteLineItem(variantInCart.id)
162
+ }
163
+ } catch (e) {
164
+ console.error(e)
165
+ } finally {
166
+ setIsAdding(false)
167
+ }
168
+ }, [variantInCart, isAdding])
169
+
170
+ const canAddToCart = useMemo(() => {
171
+ const allOptionsSelected = product.options?.every((opt) => options[opt.id])
172
+ return inStock && isValidVariant && (!product.options?.length || allOptionsSelected)
173
+ }, [inStock, isValidVariant, product.options, options])
174
+
175
+ return {
176
+ quantity,
177
+ setQuantity,
178
+ isAdding,
179
+ isBuyingNow,
180
+ variantInCart,
181
+ quantityInCart,
182
+ inStock,
183
+ inventoryLimit,
184
+ handleAddToCart,
185
+ handleBuyNow,
186
+ handleIncreaseQuantity,
187
+ handleDecreaseQuantity,
188
+ canAddToCart,
189
+ }
190
+ }
@@ -0,0 +1,18 @@
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import { fetchReviewsForProduct } from "@core/data/reviews"
5
+
6
+ export function useProductReviews(productId: string | undefined) {
7
+ const [reviews, setReviews] = useState<unknown[]>([])
8
+
9
+ useEffect(() => {
10
+ if (!productId) return
11
+
12
+ fetchReviewsForProduct(productId)
13
+ .then(setReviews)
14
+ .catch(() => setReviews([]))
15
+ }, [productId])
16
+
17
+ return reviews
18
+ }
@@ -0,0 +1,142 @@
1
+ "use client"
2
+
3
+ import { useEffect, useMemo, useState, useCallback } from "react"
4
+ import { usePathname, useSearchParams } from "next/navigation"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import {
7
+ type ProductOptions,
8
+ getInitialOptions,
9
+ findVariantByOptions,
10
+ isValidVariantSelection,
11
+ findColorOption,
12
+ findSizeOption,
13
+ getUniqueColorVariants,
14
+ getOtherOptions,
15
+ getMissingOptionIds,
16
+ } from "@core/domain/product/variant-selection"
17
+ import { useProductContext } from "@modules/products/context/product-context"
18
+
19
+ type UseProductVariantOptions = {
20
+ product: HttpTypes.StoreProduct
21
+ }
22
+
23
+ export function useProductVariant({ product }: UseProductVariantOptions) {
24
+ const pathname = usePathname()
25
+ const searchParams = useSearchParams()
26
+ const { setSelectedVariantId, setSelectedOptions } = useProductContext()
27
+
28
+ const [options, setOptions] = useState<ProductOptions>(() =>
29
+ getInitialOptions(product, searchParams.get("v_id"))
30
+ )
31
+ const [validationErrors, setValidationErrors] = useState<Record<string, boolean>>({})
32
+
33
+ useEffect(() => {
34
+ const variantIdFromUrl = searchParams.get("v_id")
35
+ if (variantIdFromUrl && product.variants) {
36
+ const variantFromUrl = product.variants.find((v) => v.id === variantIdFromUrl)
37
+ if (variantFromUrl) {
38
+ setOptions(getInitialOptions(product, variantIdFromUrl))
39
+ }
40
+ }
41
+ }, [product, searchParams])
42
+
43
+ const selectedVariant = useMemo(
44
+ () => findVariantByOptions(product, options),
45
+ [product, options]
46
+ )
47
+
48
+ const isValidVariant = useMemo(
49
+ () => isValidVariantSelection(product, options),
50
+ [product, options]
51
+ )
52
+
53
+ useEffect(() => {
54
+ setSelectedOptions(options)
55
+ }, [options, setSelectedOptions])
56
+
57
+ const setOptionValue = useCallback((optionId: string, value: string) => {
58
+ setOptions((prev) => ({
59
+ ...prev,
60
+ [optionId]: value,
61
+ }))
62
+ setValidationErrors((prev) => ({
63
+ ...prev,
64
+ [optionId]: false,
65
+ }))
66
+ }, [])
67
+
68
+ const setColorValue = useCallback((colorOptionId: string, value: string) => {
69
+ setOptions((prev) => ({
70
+ ...prev,
71
+ [colorOptionId]: value,
72
+ }))
73
+ setValidationErrors((prev) => ({
74
+ ...prev,
75
+ [colorOptionId]: false,
76
+ }))
77
+ }, [])
78
+
79
+ const validateOptions = useCallback(() => {
80
+ const missing = getMissingOptionIds(product, options)
81
+ if (Object.keys(missing).length > 0) {
82
+ setValidationErrors(missing)
83
+ return false
84
+ }
85
+ return true
86
+ }, [product, options])
87
+
88
+ useEffect(() => {
89
+ const params = new URLSearchParams(searchParams.toString())
90
+ const value = isValidVariant ? selectedVariant?.id : undefined
91
+
92
+ setSelectedVariantId(value)
93
+
94
+ if (params.get("v_id") === value) {
95
+ return
96
+ }
97
+
98
+ if (value) {
99
+ params.set("v_id", value)
100
+ } else {
101
+ params.delete("v_id")
102
+ }
103
+
104
+ const newUrl = pathname + "?" + params.toString()
105
+ window.history.replaceState(null, "", newUrl)
106
+ }, [selectedVariant, isValidVariant, pathname, searchParams, setSelectedVariantId])
107
+
108
+ const colorOption = useMemo(() => findColorOption(product), [product])
109
+ const sizeOption = useMemo(() => findSizeOption(product), [product])
110
+ const otherOptions = useMemo(() => getOtherOptions(product), [product])
111
+
112
+ const colorVariants = useMemo(() => {
113
+ if (!colorOption) {
114
+ return []
115
+ }
116
+ return getUniqueColorVariants(product, colorOption)
117
+ }, [product, colorOption])
118
+
119
+ const selectedColorValue = useMemo(() => {
120
+ if (!colorOption) {
121
+ return null
122
+ }
123
+ return options[colorOption.id]
124
+ }, [options, colorOption])
125
+
126
+ return {
127
+ options,
128
+ setOptions,
129
+ setOptionValue,
130
+ setColorValue,
131
+ selectedVariant,
132
+ isValidVariant,
133
+ validationErrors,
134
+ setValidationErrors,
135
+ validateOptions,
136
+ colorOption,
137
+ sizeOption,
138
+ otherOptions,
139
+ colorVariants,
140
+ selectedColorValue,
141
+ }
142
+ }
@@ -0,0 +1,30 @@
1
+ "use client"
2
+
3
+ import { useCallback, useState, useTransition } from "react"
4
+
5
+ type ServerAction<TInput, TOutput> = (input: TInput) => Promise<TOutput> | TOutput
6
+
7
+ export function useServerAction<TInput, TOutput>(
8
+ action: ServerAction<TInput, TOutput>
9
+ ) {
10
+ const [isPending, startTransition] = useTransition()
11
+ const [error, setError] = useState<Error | null>(null)
12
+ const [result, setResult] = useState<TOutput | null>(null)
13
+
14
+ const execute = useCallback(
15
+ (input: TInput) => {
16
+ setError(null)
17
+ startTransition(async () => {
18
+ try {
19
+ const output = await action(input)
20
+ setResult(output)
21
+ } catch (err) {
22
+ setError(err instanceof Error ? err : new Error(String(err)))
23
+ }
24
+ })
25
+ },
26
+ [action]
27
+ )
28
+
29
+ return { execute, isPending, error, result }
30
+ }
@@ -0,0 +1,46 @@
1
+ import { useState } from "react"
2
+
3
+ export type StateType = [boolean, () => void, () => void, () => void] & {
4
+ state: boolean
5
+ open: () => void
6
+ close: () => void
7
+ toggle: () => void
8
+ }
9
+
10
+ /**
11
+ *
12
+ * @param initialState - boolean
13
+ * @returns An array like object with `state`, `open`, `close`, and `toggle` properties
14
+ * to allow both object and array destructuring
15
+ *
16
+ * ```
17
+ * const [showModal, openModal, closeModal, toggleModal] = useToggleState()
18
+ * // or
19
+ * const { state, open, close, toggle } = useToggleState()
20
+ * ```
21
+ */
22
+
23
+ const useToggleState = (initialState = false) => {
24
+ const [state, setState] = useState<boolean>(initialState)
25
+
26
+ const close = () => {
27
+ setState(false)
28
+ }
29
+
30
+ const open = () => {
31
+ setState(true)
32
+ }
33
+
34
+ const toggle = () => {
35
+ setState((state) => !state)
36
+ }
37
+
38
+ const hookData = [state, open, close, toggle] as StateType
39
+ hookData.state = state
40
+ hookData.open = open
41
+ hookData.close = close
42
+ hookData.toggle = toggle
43
+ return hookData
44
+ }
45
+
46
+ export default useToggleState
@@ -0,0 +1,3 @@
1
+ "use client"
2
+
3
+ export { useWishlist } from "@core/context/wishlist-context"
@@ -0,0 +1,12 @@
1
+ /** CSS variable references for inline styles (maps to active theme.css). */
2
+ export const themeColors = {
3
+ brandAccent: "var(--color-brand-accent)",
4
+ brandAccentHover: "var(--color-brand-accent-hover)",
5
+ brandAccentMuted: "var(--color-brand-accent-muted)",
6
+ brandAccentBorder: "var(--color-brand-accent-border)",
7
+ border: "var(--color-border)",
8
+ surface: "var(--color-surface)",
9
+ pageBg: "var(--color-page-bg)",
10
+ textHeading: "var(--color-text-heading)",
11
+ textInverse: "var(--color-text-inverse)",
12
+ } as const
@@ -0,0 +1,21 @@
1
+ export type AccountPageData = {
2
+ countryCode: string
3
+ }
4
+
5
+ export enum LOGIN_VIEW {
6
+ SIGN_IN = "sign-in",
7
+ REGISTER = "register",
8
+ FORGOT_PASSWORD = "forgot-password",
9
+ }
10
+
11
+ export type LoginSlotProps = {
12
+ setCurrentView: (view: LOGIN_VIEW) => void
13
+ }
14
+
15
+ export type RegisterSlotProps = {
16
+ setCurrentView: (view: LOGIN_VIEW) => void
17
+ }
18
+
19
+ export type ForgotPasswordSlotProps = {
20
+ setCurrentView: (view: LOGIN_VIEW) => void
21
+ }
@@ -0,0 +1,13 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+
3
+ export type AbandonedCartsData = {
4
+ buyNowCarts: HttpTypes.StoreCart[]
5
+ reorderCarts: HttpTypes.StoreCart[]
6
+ }
7
+
8
+ export type CartPageData = {
9
+ cart: HttpTypes.StoreCart | null
10
+ customer: HttpTypes.StoreCustomer | null
11
+ countryCode: string
12
+ abandonedCarts: AbandonedCartsData
13
+ }
@@ -0,0 +1,52 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+
3
+ export type BannerData = {
4
+ image: string
5
+ title: string
6
+ subtitle: string
7
+ description: string
8
+ buttonName: string
9
+ buttonLink: string
10
+ }
11
+
12
+ export type HeroBlockData = {
13
+ homeBanner: BannerData
14
+ appBanner: BannerData
15
+ }
16
+
17
+ export type WhyChooseUsBlockData = {
18
+ title: string
19
+ features: Array<{ name: string; icon: string }>
20
+ }
21
+
22
+ export type TestimonialsBlockData = {
23
+ initialData: {
24
+ title: string
25
+ testimonials: Array<{
26
+ id: string
27
+ text: string
28
+ name: string
29
+ rating: number
30
+ avatar?: string
31
+ }>
32
+ } | null
33
+ }
34
+
35
+ export type HomePageData = {
36
+ hero: HeroBlockData
37
+ shopByAge: { collections: HttpTypes.StoreCollection[] }
38
+ shopByCategory: { categories: HttpTypes.StoreProductCategory[] }
39
+ whyChooseUs: WhyChooseUsBlockData
40
+ newArrivals: {
41
+ products: HttpTypes.StoreProduct[]
42
+ region: HttpTypes.StoreRegion
43
+ ratings: unknown[]
44
+ }
45
+ lovedByMoms: {
46
+ products: HttpTypes.StoreProduct[]
47
+ region: HttpTypes.StoreRegion
48
+ ratings: unknown[]
49
+ }
50
+ testimonials: TestimonialsBlockData
51
+ features: Record<string, never>
52
+ }
@@ -0,0 +1,29 @@
1
+ import { HttpTypes, StoreRegion } from "@medusajs/types"
2
+ import { StoreCartShippingOption } from "@medusajs/types"
3
+
4
+ export type NavSlotData = {
5
+ regions: StoreRegion[]
6
+ currentLocale: string
7
+ customer: HttpTypes.StoreCustomer | null
8
+ }
9
+
10
+ export type PromoBarSlotData = {
11
+ text: string
12
+ code: string
13
+ value: string
14
+ active: boolean
15
+ }
16
+
17
+ export type FooterSlotData = {
18
+ categories: HttpTypes.StoreProductCategory[]
19
+ socialLinks: Array<{ name: string; url: string; icon: string | null }>
20
+ }
21
+
22
+ export type MainLayoutData = {
23
+ customer: HttpTypes.StoreCustomer | null
24
+ cart: HttpTypes.StoreCart | null
25
+ shippingOptions: StoreCartShippingOption[]
26
+ nav: NavSlotData
27
+ promoBar: PromoBarSlotData
28
+ footer: FooterSlotData
29
+ }
@@ -0,0 +1,17 @@
1
+ import type { HttpTypes } from "@medusajs/types"
2
+
3
+ export type ProductCardRating = {
4
+ product_id: string
5
+ average_rating: number
6
+ total_reviews: number
7
+ }
8
+
9
+ /** Shared props contract for theme ProductCard slots. */
10
+ export type ProductCardProps = {
11
+ product: HttpTypes.StoreProduct
12
+ region: HttpTypes.StoreRegion
13
+ rating?: ProductCardRating
14
+ isFeatured?: boolean
15
+ wishlistProductIds?: string[] | null
16
+ className?: string
17
+ }
@@ -0,0 +1,28 @@
1
+ import { isEqual, pick } from "lodash"
2
+
3
+ export default function compareAddresses(address1: any, address2: any) {
4
+ return isEqual(
5
+ pick(address1, [
6
+ "first_name",
7
+ "last_name",
8
+ "address_1",
9
+ "company",
10
+ "postal_code",
11
+ "city",
12
+ "country_code",
13
+ "province",
14
+ "phone",
15
+ ]),
16
+ pick(address2, [
17
+ "first_name",
18
+ "last_name",
19
+ "address_1",
20
+ "company",
21
+ "postal_code",
22
+ "city",
23
+ "country_code",
24
+ "province",
25
+ "phone",
26
+ ])
27
+ )
28
+ }
@@ -0,0 +1,3 @@
1
+ export const getBaseURL = () => {
2
+ return process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:8000"
3
+ }
@@ -0,0 +1,8 @@
1
+ import { getLocale } from "@core/data/locale-actions"
2
+
3
+ export async function getLocaleHeader() {
4
+ const locale = await getLocale()
5
+ return {
6
+ "x-medusa-locale": locale,
7
+ } as const
8
+ }
@@ -0,0 +1,6 @@
1
+ export const getPercentageDiff = (original: number, calculated: number) => {
2
+ const diff = original - calculated
3
+ const decrease = (diff / original) * 100
4
+
5
+ return decrease.toFixed()
6
+ }