@pradip1995/segment-powerhouse-carousel 0.1.2 → 0.1.4
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 +2 -1
- package/src/carousel.css +56 -1
- package/src/carousel.tsx +8 -1
- package/src/product-card.tsx +58 -19
- package/src/segment.tsx +7 -1
- package/src/wishlist-button.tsx +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-powerhouse-carousel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@pradip1995/commerce-core": "^4.0.0",
|
|
26
26
|
"@pradip1995/medusa-connector": "^0.2.0",
|
|
27
|
+
"@pradip1995/segment-login-popup": "^0.1.2",
|
|
27
28
|
"@pradip1995/segment-primitives": "^0.4.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
package/src/carousel.css
CHANGED
|
@@ -100,6 +100,10 @@
|
|
|
100
100
|
background: #fff;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
.powerhouse-carousel__media-wrap {
|
|
104
|
+
position: relative;
|
|
105
|
+
}
|
|
106
|
+
|
|
103
107
|
.powerhouse-carousel__card-media {
|
|
104
108
|
position: relative;
|
|
105
109
|
display: block;
|
|
@@ -108,6 +112,51 @@
|
|
|
108
112
|
background: #f3f3f3;
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
.powerhouse-carousel__wishlist {
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: 0.5rem;
|
|
118
|
+
right: 0.5rem;
|
|
119
|
+
z-index: 2;
|
|
120
|
+
display: inline-flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
width: 2.25rem;
|
|
124
|
+
height: 2.25rem;
|
|
125
|
+
padding: 0;
|
|
126
|
+
border: 1px solid rgba(29, 29, 29, 0.12);
|
|
127
|
+
background: rgba(255, 255, 255, 0.94);
|
|
128
|
+
color: #1d1d1d;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: color 0.2s ease, border-color 0.2s ease;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.powerhouse-carousel__wishlist:hover {
|
|
134
|
+
border-color: #1d1d1d;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.powerhouse-carousel__wishlist.is-active {
|
|
138
|
+
color: #c1272d;
|
|
139
|
+
border-color: #c1272d;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.powerhouse-carousel__wishlist.is-loading {
|
|
143
|
+
opacity: 0.55;
|
|
144
|
+
cursor: wait;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.powerhouse-carousel__wishlist-icon {
|
|
148
|
+
width: 1.05rem;
|
|
149
|
+
height: 1.05rem;
|
|
150
|
+
fill: none;
|
|
151
|
+
stroke: currentColor;
|
|
152
|
+
stroke-width: 1.75;
|
|
153
|
+
stroke-linejoin: round;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.powerhouse-carousel__wishlist.is-active .powerhouse-carousel__wishlist-icon {
|
|
157
|
+
fill: currentColor;
|
|
158
|
+
}
|
|
159
|
+
|
|
111
160
|
.powerhouse-carousel__card-image,
|
|
112
161
|
.powerhouse-carousel__card-placeholder {
|
|
113
162
|
width: 100%;
|
|
@@ -242,9 +291,15 @@
|
|
|
242
291
|
background: #f1eeae;
|
|
243
292
|
border: 1px solid #f2f682;
|
|
244
293
|
color: #1d1d1d;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.powerhouse-carousel__quickshop:disabled {
|
|
298
|
+
opacity: 0.55;
|
|
299
|
+
cursor: not-allowed;
|
|
245
300
|
}
|
|
246
301
|
|
|
247
|
-
.powerhouse-carousel__quickshop:hover {
|
|
302
|
+
.powerhouse-carousel__quickshop:hover:not(:disabled) {
|
|
248
303
|
background: #f4f1be;
|
|
249
304
|
}
|
|
250
305
|
|
package/src/carousel.tsx
CHANGED
|
@@ -20,10 +20,12 @@ export default function ProductCarousel({
|
|
|
20
20
|
products,
|
|
21
21
|
region,
|
|
22
22
|
countryCode,
|
|
23
|
+
wishlistProductIds,
|
|
23
24
|
}: {
|
|
24
25
|
products: HttpTypes.StoreProduct[]
|
|
25
26
|
region?: HttpTypes.StoreRegion
|
|
26
27
|
countryCode: string
|
|
28
|
+
wishlistProductIds?: string[]
|
|
27
29
|
}) {
|
|
28
30
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
29
31
|
const [canPrev, setCanPrev] = useState(false)
|
|
@@ -87,7 +89,12 @@ export default function ProductCarousel({
|
|
|
87
89
|
<div ref={trackRef} className="powerhouse-carousel__track">
|
|
88
90
|
{products.map((product) => (
|
|
89
91
|
<div key={product.id} className="powerhouse-carousel__slide">
|
|
90
|
-
<ProductCard
|
|
92
|
+
<ProductCard
|
|
93
|
+
product={product}
|
|
94
|
+
region={region}
|
|
95
|
+
countryCode={countryCode}
|
|
96
|
+
wishlistProductIds={wishlistProductIds}
|
|
97
|
+
/>
|
|
91
98
|
</div>
|
|
92
99
|
))}
|
|
93
100
|
</div>
|
package/src/product-card.tsx
CHANGED
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from "react"
|
|
4
4
|
import { useRouter } from "next/navigation"
|
|
5
|
-
import { addToCart } from "@pradip1995/commerce-core/client/actions/cart"
|
|
5
|
+
import { addToCart, buyNow } from "@pradip1995/commerce-core/client/actions/cart"
|
|
6
6
|
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
7
7
|
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
8
8
|
import { isNextRedirect } from "@pradip1995/segment-primitives/is-next-redirect"
|
|
9
9
|
import type { HttpTypes } from "@medusajs/types"
|
|
10
|
+
import WishlistButton from "./wishlist-button"
|
|
10
11
|
|
|
11
12
|
type Props = {
|
|
12
13
|
product: HttpTypes.StoreProduct
|
|
13
14
|
region?: HttpTypes.StoreRegion
|
|
14
15
|
countryCode: string
|
|
16
|
+
wishlistProductIds?: string[]
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export default function ProductCard({ product, region, countryCode }: Props) {
|
|
19
|
+
export default function ProductCard({ product, region, countryCode, wishlistProductIds }: Props) {
|
|
18
20
|
const router = useRouter()
|
|
19
21
|
const [adding, setAdding] = useState(false)
|
|
22
|
+
const [buying, setBuying] = useState(false)
|
|
20
23
|
const variant = product.variants?.[0]
|
|
21
24
|
const price = variant?.calculated_price?.calculated_amount
|
|
22
25
|
const originalPrice = variant?.calculated_price?.original_amount
|
|
@@ -30,17 +33,19 @@ export default function ProductCard({ product, region, countryCode }: Props) {
|
|
|
30
33
|
(inventory ?? 0) > 0
|
|
31
34
|
const href = `/products/${product.handle}`
|
|
32
35
|
const hasOptions = (product.variants?.length ?? 0) > 1
|
|
36
|
+
const busy = adding || buying
|
|
37
|
+
const code = countryCode.toLowerCase()
|
|
33
38
|
|
|
34
39
|
const handleAdd = async (event: React.MouseEvent) => {
|
|
35
40
|
event.preventDefault()
|
|
36
41
|
event.stopPropagation()
|
|
37
|
-
if (!variant?.id ||
|
|
42
|
+
if (!variant?.id || busy || !inStock || hasOptions) return
|
|
38
43
|
setAdding(true)
|
|
39
44
|
try {
|
|
40
45
|
await addToCart({
|
|
41
46
|
variantId: variant.id,
|
|
42
47
|
quantity: 1,
|
|
43
|
-
countryCode:
|
|
48
|
+
countryCode: code,
|
|
44
49
|
})
|
|
45
50
|
window.dispatchEvent(new CustomEvent("cart:item-added", { detail: { message: "Added to cart" } }))
|
|
46
51
|
router.refresh()
|
|
@@ -53,19 +58,46 @@ export default function ProductCard({ product, region, countryCode }: Props) {
|
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
const handleBuyNow = async (event: React.MouseEvent) => {
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
event.stopPropagation()
|
|
64
|
+
if (!variant?.id || busy || !inStock) return
|
|
65
|
+
setBuying(true)
|
|
66
|
+
try {
|
|
67
|
+
await buyNow({
|
|
68
|
+
variantId: variant.id,
|
|
69
|
+
quantity: 1,
|
|
70
|
+
countryCode: code,
|
|
71
|
+
})
|
|
72
|
+
router.refresh()
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (isNextRedirect(err)) throw err
|
|
75
|
+
const message = err instanceof Error ? err.message : "Buy now failed"
|
|
76
|
+
window.dispatchEvent(new CustomEvent("cart:item-added", { detail: { message, variant: "error" } }))
|
|
77
|
+
setBuying(false)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
56
81
|
return (
|
|
57
82
|
<article className={`powerhouse-carousel__card${onSale ? " is-sale" : ""}`}>
|
|
58
|
-
<
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
<div className="powerhouse-carousel__media-wrap">
|
|
84
|
+
<LocalizedLink href={href} countryCode={countryCode} className="powerhouse-carousel__card-media">
|
|
85
|
+
{product.thumbnail?.trim() ? (
|
|
86
|
+
<img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-carousel__card-image" />
|
|
87
|
+
) : (
|
|
88
|
+
<div className="powerhouse-carousel__card-placeholder" />
|
|
89
|
+
)}
|
|
90
|
+
{onSale && saved != null ? (
|
|
91
|
+
<span className="powerhouse-carousel__badge">Save {formatPrice(saved, currency)}</span>
|
|
92
|
+
) : null}
|
|
93
|
+
{!inStock ? <span className="powerhouse-carousel__sold">Sold out</span> : null}
|
|
94
|
+
</LocalizedLink>
|
|
95
|
+
<WishlistButton
|
|
96
|
+
productId={product.id}
|
|
97
|
+
countryCode={countryCode}
|
|
98
|
+
wishlistProductIds={wishlistProductIds}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
69
101
|
|
|
70
102
|
<div className="powerhouse-carousel__card-info">
|
|
71
103
|
{price != null ? (
|
|
@@ -92,9 +124,16 @@ export default function ProductCard({ product, region, countryCode }: Props) {
|
|
|
92
124
|
)}
|
|
93
125
|
|
|
94
126
|
<div className="powerhouse-carousel__actions">
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
{inStock ? (
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
className="powerhouse-carousel__quickshop"
|
|
131
|
+
onClick={handleBuyNow}
|
|
132
|
+
disabled={busy || !variant?.id}
|
|
133
|
+
>
|
|
134
|
+
{buying ? "Processing…" : "Buy now"}
|
|
135
|
+
</button>
|
|
136
|
+
) : null}
|
|
98
137
|
{hasOptions ? (
|
|
99
138
|
<LocalizedLink href={href} countryCode={countryCode} className="powerhouse-carousel__atc">
|
|
100
139
|
Choose options
|
|
@@ -104,7 +143,7 @@ export default function ProductCard({ product, region, countryCode }: Props) {
|
|
|
104
143
|
type="button"
|
|
105
144
|
className="powerhouse-carousel__atc"
|
|
106
145
|
onClick={handleAdd}
|
|
107
|
-
disabled={
|
|
146
|
+
disabled={busy || !variant?.id || !inStock}
|
|
108
147
|
>
|
|
109
148
|
{adding ? "Adding…" : !inStock ? "Sold out" : "Add to cart"}
|
|
110
149
|
</button>
|
package/src/segment.tsx
CHANGED
|
@@ -10,6 +10,7 @@ export default async function PowerhouseCarousel({
|
|
|
10
10
|
countryCode: countryCodeProp,
|
|
11
11
|
title = DEFAULT_TITLE,
|
|
12
12
|
emptyMessage = DEFAULT_EMPTY,
|
|
13
|
+
wishlistProductIds,
|
|
13
14
|
}: PowerhouseCarouselProps) {
|
|
14
15
|
const countryCode =
|
|
15
16
|
countryCodeProp?.toLowerCase() ||
|
|
@@ -47,7 +48,12 @@ export default async function PowerhouseCarousel({
|
|
|
47
48
|
{list.length === 0 ? (
|
|
48
49
|
<p className="powerhouse-carousel__empty">{emptyMessage}</p>
|
|
49
50
|
) : (
|
|
50
|
-
<ProductCarousel
|
|
51
|
+
<ProductCarousel
|
|
52
|
+
products={list}
|
|
53
|
+
region={displayRegion ?? undefined}
|
|
54
|
+
countryCode={countryCode}
|
|
55
|
+
wishlistProductIds={wishlistProductIds}
|
|
56
|
+
/>
|
|
51
57
|
)}
|
|
52
58
|
</section>
|
|
53
59
|
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type MouseEvent } from "react"
|
|
4
|
+
import { useRouter } from "next/navigation"
|
|
5
|
+
import {
|
|
6
|
+
addToWishlist,
|
|
7
|
+
getWishlistProductIds,
|
|
8
|
+
removeFromWishlist,
|
|
9
|
+
} from "@pradip1995/commerce-core/client/actions/wishlist"
|
|
10
|
+
import LoginRequiredModal from "@pradip1995/segment-login-popup/required"
|
|
11
|
+
|
|
12
|
+
type WishlistButtonProps = {
|
|
13
|
+
productId?: string | null
|
|
14
|
+
countryCode: string
|
|
15
|
+
wishlistProductIds?: string[]
|
|
16
|
+
className?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function WishlistButton({
|
|
20
|
+
productId,
|
|
21
|
+
countryCode,
|
|
22
|
+
wishlistProductIds,
|
|
23
|
+
className = "powerhouse-carousel__wishlist",
|
|
24
|
+
}: WishlistButtonProps) {
|
|
25
|
+
const router = useRouter()
|
|
26
|
+
const [wishlisted, setWishlisted] = useState(false)
|
|
27
|
+
const [loading, setLoading] = useState(false)
|
|
28
|
+
const [loginRequired, setLoginRequired] = useState(false)
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!productId || wishlistProductIds === undefined) return
|
|
32
|
+
setWishlisted(wishlistProductIds.includes(productId))
|
|
33
|
+
}, [productId, wishlistProductIds])
|
|
34
|
+
|
|
35
|
+
async function handleToggle(event: MouseEvent) {
|
|
36
|
+
event.preventDefault()
|
|
37
|
+
event.stopPropagation()
|
|
38
|
+
if (!productId || loading) return
|
|
39
|
+
|
|
40
|
+
setLoading(true)
|
|
41
|
+
try {
|
|
42
|
+
let currentlyWishlisted = wishlisted
|
|
43
|
+
if (wishlistProductIds === undefined) {
|
|
44
|
+
try {
|
|
45
|
+
const ids = await getWishlistProductIds()
|
|
46
|
+
currentlyWishlisted = ids.includes(productId)
|
|
47
|
+
setWishlisted(currentlyWishlisted)
|
|
48
|
+
} catch {
|
|
49
|
+
currentlyWishlisted = false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = currentlyWishlisted
|
|
54
|
+
? await removeFromWishlist(productId)
|
|
55
|
+
: await addToWishlist(productId)
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
setWishlisted(!currentlyWishlisted)
|
|
59
|
+
router.refresh()
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (result.error?.toLowerCase().includes("login")) {
|
|
64
|
+
setLoginRequired(true)
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
setLoading(false)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
className={`${className}${wishlisted ? " is-active" : ""}${loading ? " is-loading" : ""}`}
|
|
76
|
+
onClick={handleToggle}
|
|
77
|
+
disabled={loading || !productId}
|
|
78
|
+
aria-label={wishlisted ? "Remove from wishlist" : "Add to wishlist"}
|
|
79
|
+
aria-pressed={wishlisted}
|
|
80
|
+
>
|
|
81
|
+
<svg viewBox="0 0 24 24" aria-hidden className={`${className}-icon`}>
|
|
82
|
+
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
<LoginRequiredModal
|
|
86
|
+
isOpen={loginRequired}
|
|
87
|
+
onClose={() => setLoginRequired(false)}
|
|
88
|
+
countryCode={countryCode}
|
|
89
|
+
message="Please log in to save items to your wishlist."
|
|
90
|
+
/>
|
|
91
|
+
</>
|
|
92
|
+
)
|
|
93
|
+
}
|