medusa-ui-common 2.3.0 → 3.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusa-ui-common",
3
- "version": "2.3.0",
3
+ "version": "3.0.1",
4
4
  "description": "Shared storefront UI primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "medusa-storefront-data": "^2.0.0",
27
27
  "medusa-storefront-hooks": "^1.0.0",
28
28
  "medusa-wishlist-logic": "^2.0.0",
29
- "medusa-storefront-theme-base": "^2.0.0",
29
+ "medusa-storefront-theme-base": "^3.0.0",
30
30
  "next": ">=14.0.0",
31
31
  "react": "^18.0.0 || ^19.0.0",
32
32
  "react-dom": "^18.0.0 || ^19.0.0"
@@ -48,21 +48,6 @@
48
48
  "import": "./src/common/*",
49
49
  "default": "./src/common/*"
50
50
  },
51
- "./providers": {
52
- "types": "./src/storefront-providers.tsx",
53
- "import": "./src/storefront-providers.tsx",
54
- "default": "./src/storefront-providers.tsx"
55
- },
56
- "./context/wishlist-context": {
57
- "types": "./src/context/wishlist-context.tsx",
58
- "import": "./src/context/wishlist-context.tsx",
59
- "default": "./src/context/wishlist-context.tsx"
60
- },
61
- "./context/modal-context": {
62
- "types": "./src/context/modal-context.tsx",
63
- "import": "./src/context/modal-context.tsx",
64
- "default": "./src/context/modal-context.tsx"
65
- },
66
51
  "./context/*": {
67
52
  "types": "./src/context/*",
68
53
  "import": "./src/context/*",
@@ -97,17 +82,17 @@
97
82
  }
98
83
  },
99
84
  "devDependencies": {
100
- "next": "^15.0.0",
101
- "medusa-storefront-analytics": "workspace:*",
102
- "medusa-storefront-core": "workspace:*",
103
- "medusa-storefront-data": "workspace:*",
104
- "medusa-storefront-theme-base": "workspace:*",
105
85
  "@headlessui/react": "^2.2.0",
106
86
  "@medusajs/icons": "^2.0.0",
107
87
  "@medusajs/ui": "^4.0.0",
108
88
  "@types/react": "18.3.18",
109
89
  "@types/react-dom": "18.3.5",
110
90
  "typescript": "^5.6.0",
91
+ "next": "^15.0.0",
92
+ "medusa-storefront-analytics": "1.5.1",
93
+ "medusa-storefront-core": "1.2.5",
94
+ "medusa-storefront-data": "2.5.9",
95
+ "medusa-storefront-theme-base": "3.0.1",
111
96
  "color": "^5.0.3",
112
97
  "react-razorpay": "^3.0.1"
113
98
  }
@@ -1,43 +1,2 @@
1
- import { HttpTypes } from "@medusajs/types"
2
- import LocalizedClientLink from "../localized-client-link"
3
-
4
- type BreadcrumbProps = {
5
- product: HttpTypes.StoreProduct
6
- }
7
-
8
- export default function Breadcrumb({ product }: BreadcrumbProps) {
9
- const primaryCategory = product.categories?.[0]
10
-
11
- return (
12
- <nav className="text-xs sm:text-sm text-[var(--sf-color-text)] mb-3 sm:mb-4 overflow-x-auto font-medium">
13
- <div className="flex items-center whitespace-nowrap">
14
- <LocalizedClientLink href="/" className="hover:text-[var(--sf-color-text)] transition-colors">
15
- Home
16
- </LocalizedClientLink>
17
- <span className="mx-2">/</span>
18
- <LocalizedClientLink href="/store" className="hover:text-[var(--sf-color-text)] transition-colors">
19
- Shop
20
- </LocalizedClientLink>
21
- {primaryCategory && (
22
- <>
23
- <span className="mx-2">/</span>
24
- <LocalizedClientLink
25
- href={`/store?category=${primaryCategory.id}`}
26
- className="hover:text-[var(--sf-color-text)] transition-colors"
27
- >
28
- {primaryCategory.name}
29
- </LocalizedClientLink>
30
- </>
31
- )}
32
- <span className="mx-2">/</span>
33
- <span className="text-[var(--sf-color-text)] truncate max-w-[150px] sm:max-w-[300px] md:max-w-[400px] inline-block align-bottom" title={product.title ?? ""}>
34
- {product.title}
35
- </span>
36
- </div>
37
- </nav>
38
- )
39
- }
40
-
41
-
42
-
43
-
1
+ /** @deprecated Import from `medusa-ui-product/products/components/breadcrumb` */
2
+ export { default } from "medusa-ui-product/products/components/breadcrumb"
@@ -12,6 +12,12 @@ import { useSiteCompany } from "medusa-storefront-core"
12
12
  import { useButtonClassName } from "../../../context/button-theme-context"
13
13
  import { ThemedButton } from "../themed-button"
14
14
 
15
+ type CartTotalsPriceSummaryCopy = {
16
+ title: string
17
+ itemSingular: string
18
+ itemPlural: string
19
+ }
20
+
15
21
  type CartTotalsProps = {
16
22
  totals: {
17
23
  total?: number | null
@@ -25,9 +31,16 @@ type CartTotalsProps = {
25
31
  items?: HttpTypes.StoreCartLineItem[]
26
32
  checkoutStep?: string
27
33
  customer?: HttpTypes.StoreCustomer | null
34
+ priceSummaryCopy?: CartTotalsPriceSummaryCopy
28
35
  }
29
36
 
30
- const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutStep, customer }) => {
37
+ const CartTotals: React.FC<CartTotalsProps> = ({
38
+ totals,
39
+ items = [],
40
+ checkoutStep,
41
+ customer,
42
+ priceSummaryCopy,
43
+ }) => {
31
44
  const company = useSiteCompany()
32
45
  const primaryBtn = useButtonClassName("primary", { size: "lg", className: "w-full" })
33
46
  const secondaryBtn = useButtonClassName("secondary", { size: "lg", className: "w-full" })
@@ -277,7 +290,9 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
277
290
  <div className="bg-[var(--sf-color-background)] rounded-lg p-3 min-[550px]:p-4 sm:p-5 md:p-6 min-[1023px]:p-4 min-[1150px]:p-5 min-[1360px]:p-6 border shadow-sm w-full" style={{ borderColor: '#C0C0C0' }}>
278
291
  <ProcessingOverlay isOpen={showOverlay} variant={processingVariant} />
279
292
  <h3 className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg font-bold text-[var(--sf-color-text)] mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
280
- PRICE SUMMARY ({itemCount} {itemCount === 1 ? "ITEM" : "ITEMS"})
293
+ {priceSummaryCopy
294
+ ? `${priceSummaryCopy.title} (${itemCount} ${itemCount === 1 ? priceSummaryCopy.itemSingular : priceSummaryCopy.itemPlural})`
295
+ : `PRICE SUMMARY (${itemCount} ${itemCount === 1 ? "ITEM" : "ITEMS"})`}
281
296
  </h3>
282
297
 
283
298
  <div className="flex flex-col gap-y-2 min-[550px]:gap-y-2.5 sm:gap-y-3 min-[1023px]:gap-y-2 min-[1150px]:gap-y-3 min-[1360px]:gap-y-3 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-[var(--sf-color-text)]">
@@ -1,4 +1,3 @@
1
- import { Label } from "@medusajs/ui"
2
1
  import React from "react"
3
2
  import Color from "color"
4
3
 
@@ -43,18 +42,21 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
43
42
  }
44
43
  }, [hex, color])
45
44
 
45
+ const labelId = React.useId()
46
+
46
47
  return (
47
48
  <div className="flex items-center space-x-2 px-1 py-0.5">
48
49
  <button
49
50
  type="button"
50
51
  role="checkbox"
51
52
  aria-checked={checked}
53
+ aria-labelledby={labelId}
52
54
  onClick={onChange}
53
55
  name={name}
54
56
  data-testid={dataTestId}
55
57
  data-ga-event={dataGaEvent}
56
58
  data-ga-label={dataGaLabel}
57
- className="w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
59
+ className="w-5 h-5 shrink-0 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
58
60
  style={checked ? {
59
61
  backgroundColor: 'var(--sf-btn-primary)',
60
62
  borderColor: 'var(--sf-btn-primary)'
@@ -77,11 +79,17 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
77
79
  </svg>
78
80
  )}
79
81
  </button>
80
- <Label
81
- htmlFor={name || "checkbox"}
82
- className="!transform-none !txt-medium cursor-pointer flex items-center gap-2"
83
- size="large"
82
+ <span
83
+ id={labelId}
84
+ role="presentation"
84
85
  onClick={onChange}
86
+ onKeyDown={(e) => {
87
+ if (e.key === "Enter" || e.key === " ") {
88
+ e.preventDefault()
89
+ onChange?.()
90
+ }
91
+ }}
92
+ className="txt-medium cursor-pointer flex items-center gap-2 text-[var(--sf-color-text)]"
85
93
  >
86
94
  {colorHex && (
87
95
  <div
@@ -90,7 +98,7 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
90
98
  />
91
99
  )}
92
100
  {label}
93
- </Label>
101
+ </span>
94
102
  </div>
95
103
  )
96
104
  }
@@ -1,134 +1,2 @@
1
- import { Heading } from "@medusajs/ui"
2
- import CheckboxWithLabel from "medusa-ui-common/common/components/checkbox"
3
- import React from "react"
4
-
5
- type FilterCheckboxGroupProps = {
6
- title: string
7
- items: { value: string; label: string }[]
8
- values: string[]
9
- handleChange: (value: string) => void
10
- isColor?: boolean
11
- 'data-testid'?: string
12
- }
13
-
14
- const FilterCheckboxGroup = ({
15
- title,
16
- items,
17
- values,
18
- handleChange,
19
- isColor,
20
- 'data-testid': dataTestId,
21
- }: FilterCheckboxGroupProps) => {
22
- const [isOpen, setIsOpen] = React.useState(true)
23
- const [showAll, setShowAll] = React.useState(false)
24
-
25
- // Optimistic local state — reflects clicks instantly without waiting for server
26
- const [optimisticValues, setOptimisticValues] = React.useState<string[]>(values)
27
-
28
- // Sync back when server props update (after navigation completes)
29
- React.useEffect(() => {
30
- setOptimisticValues(values)
31
- }, [values])
32
-
33
- const initialItemsCount = 6
34
- const hasMore = items?.length > initialItemsCount
35
-
36
- // Auto-expand if a selected item is hidden or if any item is selected
37
- React.useEffect(() => {
38
- if (values && values.length > 0) {
39
- setIsOpen(true)
40
- if (hasMore && items) {
41
- const hiddenItems = items.slice(initialItemsCount)
42
- const isAnyHiddenItemSelected = hiddenItems.some(item => values.includes(item.value))
43
- if (isAnyHiddenItemSelected) {
44
- setShowAll(true)
45
- }
46
- }
47
- }
48
- }, [values, items, hasMore])
49
-
50
- const handleOptimisticChange = (value: string) => {
51
- // Immediately toggle locally — no flicker
52
- setOptimisticValues((prev) =>
53
- prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
54
- )
55
- // Trigger actual URL navigation in background
56
- handleChange(value)
57
- }
58
-
59
- const displayedItems = showAll ? items : items?.slice(0, initialItemsCount)
60
-
61
- return (
62
- <div className="flex flex-col border-b border-gray-100 pb-4 last:border-0 last:pb-0">
63
- <button
64
- onClick={() => setIsOpen(!isOpen)}
65
- className="flex items-center justify-between w-full group transition-all py-1"
66
- >
67
- <Heading
68
- level="h3"
69
- className="text-sm font-bold text-[var(--sf-color-text)] uppercase tracking-wider group-hover:text-[var(--sf-btn-primary)] transition-colors"
70
- >
71
- {title}
72
- </Heading>
73
- <span
74
- className={`transform transition-transform duration-300 text-[var(--sf-color-text-muted)] group-hover:text-[var(--sf-btn-primary)] ${isOpen ? "" : "-rotate-180"}`}
75
- >
76
- <svg
77
- width="18"
78
- height="18"
79
- viewBox="0 0 24 24"
80
- fill="none"
81
- stroke="currentColor"
82
- strokeWidth="2.5"
83
- strokeLinecap="round"
84
- strokeLinejoin="round"
85
- >
86
- <polyline points="18 15 12 9 6 15"></polyline>
87
- </svg>
88
- </span>
89
- </button>
90
-
91
- <div
92
- className={`flex flex-col gap-y-1 transition-all duration-300 ease-in-out ${isOpen ? "mt-3 opacity-100 px-0.5" : "max-h-0 opacity-0 invisible overflow-hidden"}`}
93
- style={isOpen ? { maxHeight: showAll ? '1000px' : '300px' } : {}}
94
- >
95
- {displayedItems?.map((i) => (
96
- <CheckboxWithLabel
97
- key={i.value}
98
- label={i.label}
99
- color={isColor ? i.value : undefined}
100
- checked={optimisticValues.includes(i.value)}
101
- onChange={() => handleOptimisticChange(i.value)}
102
- data-testid={dataTestId}
103
- data-ga-event="filter_checkbox_click"
104
- data-ga-label={`${title} - ${i.label}`}
105
- />
106
- ))}
107
-
108
- {hasMore && isOpen && (
109
- <button
110
- onClick={() => setShowAll(!showAll)}
111
- className="text-xs font-bold text-[var(--sf-btn-primary)] hover:underline mt-2 flex items-center gap-1 uppercase tracking-tight"
112
- >
113
- {showAll ? "View Less" : `View More (${items.length - initialItemsCount})`}
114
- <svg
115
- width="12"
116
- height="12"
117
- viewBox="0 0 24 24"
118
- fill="none"
119
- stroke="currentColor"
120
- strokeWidth="3"
121
- strokeLinecap="round"
122
- strokeLinejoin="round"
123
- className={`transform transition-transform ${showAll ? "rotate-180" : ""}`}
124
- >
125
- <polyline points="6 9 12 15 18 9"></polyline>
126
- </svg>
127
- </button>
128
- )}
129
- </div>
130
- </div>
131
- )
132
- }
133
-
134
- export default FilterCheckboxGroup
1
+ /** @deprecated Import from `medusa-ui-product/store/components/filter-checkbox-group` */
2
+ export { default } from "medusa-ui-product/store/components/filter-checkbox-group"
@@ -1,78 +1,2 @@
1
- "use client"
2
-
3
- import { useRouter, usePathname } from "next/navigation"
4
-
5
- interface LoginPopupProps {
6
- isOpen: boolean
7
- onClose: () => void
8
- countryCode?: string
9
- message?: string
10
- }
11
-
12
- export default function LoginPopup({ isOpen, onClose, countryCode = "us", message = "Please login to add products to your wishlist." }: LoginPopupProps) {
13
- const router = useRouter()
14
- const pathname = usePathname()
15
-
16
- const handleLoginClick = () => {
17
- // Store current URL in localStorage for redirect after login
18
- if (typeof window !== "undefined") {
19
- const currentPath = pathname || window.location.pathname
20
- // Add a special marker so the login component knows to keep the user on this page
21
- const redirectWithContext = currentPath.includes('?')
22
- ? `${currentPath}&login_context=keep`
23
- : `${currentPath}?login_context=keep`
24
-
25
- localStorage.setItem("loginRedirectUrl", redirectWithContext)
26
- }
27
- router.push(`/${countryCode}/account`)
28
- }
29
-
30
- if (!isOpen) {
31
- return null
32
- }
33
-
34
- return (
35
- <div className="fixed inset-0 z-[9999] flex items-center justify-center bg-white/10 backdrop-blur-md p-4 animate-in fade-in duration-200">
36
- <div
37
- className="bg-[var(--sf-color-background)] rounded-2xl shadow-2xl p-8 max-w-sm w-full mx-auto border border-gray-100 transform transition-all scale-100 animate-in zoom-in-95 duration-200"
38
- onClick={(e) => e.stopPropagation()}
39
- >
40
- <div className="text-center">
41
- <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-purple-100 mb-4">
42
- <svg className="h-6 w-6 text-[var(--sf-btn-primary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
43
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
44
- </svg>
45
- </div>
46
- <h2 className="text-xl font-bold text-[var(--sf-color-text)] mb-2">
47
- Login Required
48
- </h2>
49
- <p className="text-[var(--sf-color-text-muted)] mb-8 text-sm leading-relaxed">
50
- {message}
51
- </p>
52
-
53
- <div className="grid grid-cols-2 gap-3">
54
- <button
55
- onClick={onClose}
56
- className="px-4 py-2.5 border border-gray-200 rounded-xl font-semibold text-[var(--sf-color-text-muted)] hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] hover:border-gray-300 active:scale-95 transition-all duration-200"
57
- >
58
- Cancel
59
- </button>
60
- <button
61
- onClick={handleLoginClick}
62
- className="px-4 py-2.5 rounded-xl font-semibold text-white transition-all duration-200 hover:shadow-lg hover:shadow-purple-100 active:scale-95"
63
- style={{ backgroundColor: 'var(--sf-btn-primary)' }}
64
- onMouseEnter={(e) => {
65
- e.currentTarget.style.backgroundColor = 'var(--sf-btn-primary-hover)'
66
- }}
67
- onMouseLeave={(e) => {
68
- e.currentTarget.style.backgroundColor = 'var(--sf-btn-primary)'
69
- }}
70
- >
71
- Login
72
- </button>
73
- </div>
74
- </div>
75
- </div>
76
- </div>
77
- )
78
- }
1
+ /** @deprecated Import from `medusa-ui-product/products/components/login-popup` */
2
+ export { default } from "medusa-ui-product/products/components/login-popup"
@@ -6,16 +6,24 @@ const ProcessingOverlay = ({
6
6
  isOpen,
7
7
  title,
8
8
  subtitle = "Please do not refresh or close this window",
9
- variant = "payment"
9
+ variant = "payment",
10
+ gifSrc = "/uploads/profile-images/t-shirt.gif",
11
+ gifAlt = "Placing order...",
12
+ paymentMessages: paymentMessagesProp,
13
+ orderMessages: orderMessagesProp,
10
14
  }: {
11
15
  isOpen: boolean
12
16
  title?: string
13
17
  subtitle?: string
14
18
  variant?: "payment" | "order"
19
+ gifSrc?: string
20
+ gifAlt?: string
21
+ paymentMessages?: string[]
22
+ orderMessages?: string[]
15
23
  }) => {
16
24
  const [messageIndex, setMessageIndex] = useState(0)
17
25
 
18
- const paymentMessages = [
26
+ const paymentMessages = paymentMessagesProp ?? [
19
27
  "Securing your transaction...",
20
28
  "Verifying with payment provider...",
21
29
  "Finalizing your order...",
@@ -23,7 +31,7 @@ const ProcessingOverlay = ({
23
31
  "Finishing up..."
24
32
  ]
25
33
 
26
- const orderMessages = [
34
+ const orderMessages = orderMessagesProp ?? [
27
35
  "Placing your order...",
28
36
  "Preparing your confirmation...",
29
37
  "Almost there...",
@@ -54,8 +62,8 @@ const ProcessingOverlay = ({
54
62
  <div className="bg-white p-8 rounded-2xl shadow-2xl border border-purple-100 flex flex-col items-center max-w-xs w-full mx-4 transform animate-in zoom-in-95 duration-300">
55
63
  <div className="relative mb-2">
56
64
  <img
57
- src="/uploads/profile-images/t-shirt.gif"
58
- alt="Placing order..."
65
+ src={gifSrc}
66
+ alt={gifAlt}
59
67
  className="w-32 h-32 object-contain mx-auto"
60
68
  />
61
69
  </div>
@@ -1,65 +1,2 @@
1
- import React, { useEffect } from 'react'
2
- import { clx } from "@medusajs/ui"
3
-
4
- type SidePanelProps = {
5
- isOpen: boolean
6
- onClose: () => void
7
- title: string
8
- children: React.ReactNode
9
- }
10
-
11
- const SidePanel: React.FC<SidePanelProps> = ({ isOpen, onClose, title, children }) => {
12
- // Prevent body scroll when panel is open
13
- useEffect(() => {
14
- if (isOpen) {
15
- document.body.style.overflow = 'hidden'
16
- } else {
17
- document.body.style.overflow = 'unset'
18
- }
19
- return () => {
20
- document.body.style.overflow = 'unset'
21
- }
22
- }, [isOpen])
23
-
24
- return (
25
- <>
26
- {/* Backdrop */}
27
- <div
28
- className={clx(
29
- "fixed inset-0 bg-black/40 backdrop-blur-sm transition-opacity duration-300 z-[999]",
30
- isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
31
- )}
32
- onClick={onClose}
33
- />
34
-
35
- {/* Panel */}
36
- <div
37
- className={clx(
38
- "fixed top-0 right-0 h-full w-[90%] min-[400px]:w-[350px] sm:w-[400px] md:w-[450px] lg:w-[480px] bg-[var(--sf-color-background)] shadow-2xl transition-transform duration-300 ease-in-out z-[1000] flex flex-col",
39
- isOpen ? "translate-x-0" : "translate-x-full"
40
- )}
41
- >
42
- {/* Header */}
43
- <div className="flex items-center justify-between p-6 border-b border-gray-100">
44
- <h2 className="text-xl font-bold text-[var(--sf-color-text)]">{title}</h2>
45
- <button
46
- onClick={onClose}
47
- className="p-2 hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_10%,transparent)] rounded-full transition-colors"
48
- >
49
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
50
- <line x1="18" y1="6" x2="6" y2="18"></line>
51
- <line x1="6" y1="6" x2="18" y2="18"></line>
52
- </svg>
53
- </button>
54
- </div>
55
-
56
- {/* Content */}
57
- <div className="flex-1 overflow-y-auto p-6">
58
- {children}
59
- </div>
60
- </div>
61
- </>
62
- )
63
- }
64
-
65
- export default SidePanel
1
+ /** @deprecated Import from `medusa-ui-product/products/components/side-panel` */
2
+ export { default } from "medusa-ui-product/products/components/side-panel"
@@ -1,31 +1,2 @@
1
- import React from "react";
2
- import { CreditCard } from "@medusajs/icons";
3
- import Ideal from "medusa-ui-common/common/icons/ideal";
4
- import Bancontact from "medusa-ui-common/common/icons/bancontact";
5
- import PayPal from "medusa-ui-common/common/icons/paypal";
6
-
7
- export const paymentInfoMap: Record<string, { title: string; icon: React.JSX.Element }> = {
8
- pp_stripe_stripe: { title: "Credit card", icon: <CreditCard /> },
9
- "pp_medusa-payments_default": { title: "Credit card", icon: <CreditCard /> },
10
- "pp_stripe-ideal_stripe": { title: "iDeal", icon: <Ideal /> },
11
- "pp_stripe-bancontact_stripe": { title: "Bancontact", icon: <Bancontact /> },
12
- pp_paypal_paypal: { title: "PayPal", icon: <PayPal /> },
13
- pp_system_default: { title: "Cash on Delivery (Cash/UPI)", icon: <CreditCard /> },
14
- razorpay: { title: "Razorpay", icon: <CreditCard /> },
15
- pp_razorpay_razorpay: { title: "Razorpay", icon: <CreditCard /> },
16
- };
17
-
18
- export const isStripeLike = (providerId?: string) =>
19
- providerId?.startsWith("pp_stripe_") || providerId?.startsWith("pp_medusa-");
20
-
21
- export const isPaypal = (providerId?: string) => providerId?.startsWith("pp_paypal");
22
-
23
- export const isManual = (providerId?: string) => providerId?.startsWith("pp_system_default");
24
-
25
- export const isRazorpay = (providerId?: string) =>
26
- providerId?.startsWith("razorpay") || providerId?.startsWith("pp_razorpay");
27
-
28
- export const noDivisionCurrencies = [
29
- "krw", "jpy", "vnd", "clp", "pyg", "xaf", "xof", "bif", "djf", "gnf", "kmf", "mga", "rwf",
30
- "xpf", "htg", "vuv", "xag", "xdr", "xau",
31
- ];
1
+ /** @deprecated Import from `medusa-ui-checkout/checkout/constants/payments` */
2
+ export * from "medusa-ui-checkout/checkout/constants/payments"
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ /** Props passed to every page section wrapper. */
4
+ export type SectionThemeProps<TTheme extends Record<string, string>> = {
5
+ theme?: Partial<TTheme>
6
+ }
7
+
8
+ /**
9
+ * Registry entry for composing a page from sections in the main app.
10
+ * @example
11
+ * const HOME_SECTIONS = {
12
+ * hero: { Component: HeroSection, load: loadHeroSectionData },
13
+ * }
14
+ */
15
+ export type PageSectionEntry<TProps, TData = TProps> = {
16
+ Component: (props: TProps) => ReactNode
17
+ load?: (ctx: PageLoadContext) => Promise<TData | null>
18
+ }
19
+
20
+ export type PageLoadContext = {
21
+ countryCode: string
22
+ pageInput?: Record<string, unknown>
23
+ }
24
+
25
+ export type PageSectionRegistry = Record<string, PageSectionEntry<unknown>>
26
+
27
+ /** Ordered list of section ids for a page variant. */
28
+ export type PageVariantDefinition<TId extends string = string> = {
29
+ id: string
30
+ sectionIds: readonly TId[]
31
+ }
@@ -74,23 +74,16 @@ export type StorefrontUiProvidersProps = {
74
74
  themeClasses?: StorefrontThemeClasses;
75
75
  /** Form field classNames — from defineSiteTheme().siteFormTheme */
76
76
  formTheme?: FormThemeClassNames;
77
- /**
78
- * @deprecated Prefer siteTheme — button CSS vars are derived from siteTheme.colors.
79
- */
80
- buttonCssVars?: Record<string, string>;
81
77
  };
82
78
 
83
79
  function ButtonThemeFromSite({
84
80
  children,
85
81
  buttonTheme,
86
- legacyButtonCssVars,
87
82
  }: {
88
83
  children: ReactNode;
89
84
  buttonTheme: ButtonTheme;
90
- legacyButtonCssVars?: Record<string, string>;
91
85
  }) {
92
- const { buttonCssVars: fromSite } = useSiteTheme();
93
- const cssVars = legacyButtonCssVars ?? fromSite;
86
+ const { buttonCssVars: cssVars } = useSiteTheme();
94
87
  return (
95
88
  <ButtonThemeProvider theme={buttonTheme} cssVars={cssVars}>
96
89
  {children}
@@ -105,12 +98,11 @@ export function StorefrontUiProviders({
105
98
  buttonTheme = defaultButtonTheme,
106
99
  themeClasses,
107
100
  formTheme,
108
- buttonCssVars,
109
101
  }: StorefrontUiProvidersProps) {
110
102
  return (
111
103
  <SiteThemeProvider theme={siteTheme}>
112
104
  <StorefrontThemeClassesProvider themeClasses={themeClasses} form={formTheme}>
113
- <ButtonThemeFromSite buttonTheme={buttonTheme} legacyButtonCssVars={buttonCssVars}>
105
+ <ButtonThemeFromSite buttonTheme={buttonTheme}>
114
106
  <WishlistProvider>{children}</WishlistProvider>
115
107
  </ButtonThemeFromSite>
116
108
  </StorefrontThemeClassesProvider>
@@ -1,65 +1,2 @@
1
- const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
2
-
3
- export function normalizeCheckoutEmail(email?: string | null): string {
4
- return (email ?? "").trim().toLowerCase()
5
- }
6
-
7
- export function isValidCheckoutEmail(email?: string | null): boolean {
8
- const normalized = normalizeCheckoutEmail(email)
9
- return normalized.length > 0 && EMAIL_PATTERN.test(normalized)
10
- }
11
-
12
- export function readCheckoutAddressFromDom(fallback?: {
13
- email?: string | null
14
- shipping_address?: Record<string, unknown> | null
15
- }) {
16
- const fullNameInput = document.getElementById("shipping_full_name_field") as HTMLInputElement | null
17
- const address1Input = document.getElementById("shipping_address_1_field") as HTMLInputElement | null
18
- const companyInput = document.getElementById("shipping_company_field") as HTMLInputElement | null
19
- const cityInput = document.getElementById("shipping_city_field") as HTMLInputElement | null
20
- const provinceInput = document.getElementById("shipping_province_field") as HTMLInputElement | null
21
- const postalInput = document.getElementById("shipping_postal_code_field") as HTMLInputElement | null
22
- const phoneInput = document.getElementById("shipping_phone_field_internal") as HTMLInputElement | null
23
- const emailInput = document.getElementById("shipping_email_field") as HTMLInputElement | null
24
-
25
- const fullName = fullNameInput?.value?.trim() || ""
26
- const [firstName, ...lastNameParts] = fullName.split(/\s+/).filter(Boolean)
27
- const lastName = lastNameParts.join(" ") || "."
28
-
29
- const shipping = fallback?.shipping_address ?? {}
30
-
31
- return {
32
- email: normalizeCheckoutEmail(emailInput?.value || (fallback?.email as string) || ""),
33
- shipping_address: {
34
- first_name: firstName || (shipping.first_name as string) || "Visitor",
35
- last_name: lastName || (shipping.last_name as string) || "Customer",
36
- address_1: address1Input?.value?.trim() || (shipping.address_1 as string) || "",
37
- company: companyInput?.value?.trim() || (shipping.company as string) || "",
38
- city: cityInput?.value?.trim() || (shipping.city as string) || "",
39
- province: provinceInput?.value?.trim() || (shipping.province as string) || "",
40
- postal_code: postalInput?.value?.trim() || (shipping.postal_code as string) || "",
41
- phone: phoneInput?.value?.trim() || (shipping.phone as string) || "",
42
- country_code: (shipping.country_code as string) || "in",
43
- },
44
- }
45
- }
46
-
47
- export async function syncCheckoutAddressFromDom(fallback?: {
48
- email?: string | null
49
- shipping_address?: Record<string, unknown> | null
50
- }) {
51
- const payload = readCheckoutAddressFromDom(fallback)
52
-
53
- if (!isValidCheckoutEmail(payload.email)) {
54
- throw new Error("Please enter a valid email address before continuing.")
55
- }
56
-
57
- const { updateAddressSilently } = await import("medusa-storefront-data/cart")
58
- const result = await updateAddressSilently(payload)
59
-
60
- if (result && "success" in result && result.success === false) {
61
- throw new Error("Could not save your delivery details. Please check your email and try again.")
62
- }
63
-
64
- return payload
65
- }
1
+ /** @deprecated Import from `medusa-ui-checkout/checkout/util/checkout-dom` */
2
+ export * from "medusa-ui-checkout/checkout/util/checkout-dom"
@@ -1,72 +1,3 @@
1
-
2
- import { HttpTypes } from "@medusajs/types"
3
-
4
- export type ItemWithDeliveryStatus = HttpTypes.StoreOrderLineItem & {
5
- returnable_quantity: number
6
- delivered_quantity: number
7
- return_requested_quantity: number
8
- return_received_quantity: number
9
- written_off_quantity: number
10
- }
11
-
12
- export const calculateReturnableQuantity = (
13
- item: HttpTypes.StoreOrderLineItem
14
- ): number => {
15
- // In a real implementation, these would come from the item.detail or similar
16
- // For now, we'll assume we can calculate it from available data or default to quantity
17
- // Adjust this based on your actual data structure for item details
18
-
19
- // Note: The actual data structure depends on how Medusa returns order item details
20
- // Typically it's in item.detail associated with fulfillments/returns
21
-
22
- // For the purpose of this storefront implementation (assuming standard Medusa):
23
- // We strictly follow the logic: delivered - requested - received - written_off
24
-
25
- // Checking if properties exist on item (they might be on a 'detail' object or top level depending on version)
26
- const anyItem = item as any
27
-
28
- // Defaulting to item.quantity if details missing (safe fallback for initial dev)
29
- // BUT per requirements, we must implement the logic.
30
-
31
- // If the backend provides these fields on the item directly:
32
- const delivered = anyItem.delivered_quantity ?? anyItem.quantity ?? 0 // Fallback to quantity if assumed delivered
33
- const requested = anyItem.return_requested_quantity ?? 0
34
- const received = anyItem.return_received_quantity ?? 0
35
- const writtenOff = anyItem.written_off_quantity ?? 0
36
-
37
- // If fulfillment_status is not fulfilled/shipped/partially_shipped, returnable might be 0
38
- // But strict formula:
39
- const returnable = Math.max(0, delivered - requested - received - writtenOff)
40
-
41
- return returnable
42
- }
43
-
44
- export const isItemReturnable = (item: HttpTypes.StoreOrderLineItem): boolean => {
45
- return calculateReturnableQuantity(item) > 0
46
- }
47
-
48
- export const hasReturnableItems = (order: HttpTypes.StoreOrder): boolean => {
49
- if (!order || !order.items) return false
50
-
51
- // Simple check: if order is canceled, no returns
52
- if (order.status === "canceled") return false
53
-
54
- return order.items.some((item) => isItemReturnable(item))
55
- }
56
-
57
- export const enhanceItemsWithReturnStatus = (
58
- items: HttpTypes.StoreOrderLineItem[]
59
- ): ItemWithDeliveryStatus[] => {
60
- return items.map((item) => {
61
- const anyItem = item as any
62
- return {
63
- ...item,
64
- // Ensure these properties exist
65
- delivered_quantity: anyItem.delivered_quantity ?? item.quantity, // Optimistic default
66
- return_requested_quantity: anyItem.return_requested_quantity ?? 0,
67
- return_received_quantity: anyItem.return_received_quantity ?? 0,
68
- written_off_quantity: anyItem.written_off_quantity ?? 0,
69
- returnable_quantity: calculateReturnableQuantity(item),
70
- }
71
- })
72
- }
1
+ /** @deprecated Account flows: `medusa-ui-account/account/util/returns`. Order checks: `medusa-ui-order/order/util/returns`. */
2
+ export * from "medusa-ui-account/account/util/returns"
3
+ export { hasReturnableItems } from "medusa-ui-order/order/util/returns"