@pradip1995/segment-powerhouse-carousel 0.1.2 → 0.1.3

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": "@pradip1995/segment-powerhouse-carousel",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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%;
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 product={product} region={region} countryCode={countryCode} />
92
+ <ProductCard
93
+ product={product}
94
+ region={region}
95
+ countryCode={countryCode}
96
+ wishlistProductIds={wishlistProductIds}
97
+ />
91
98
  </div>
92
99
  ))}
93
100
  </div>
@@ -7,14 +7,16 @@ 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)
20
22
  const variant = product.variants?.[0]
@@ -55,17 +57,24 @@ export default function ProductCard({ product, region, countryCode }: Props) {
55
57
 
56
58
  return (
57
59
  <article className={`powerhouse-carousel__card${onSale ? " is-sale" : ""}`}>
58
- <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-carousel__card-media">
59
- {product.thumbnail?.trim() ? (
60
- <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-carousel__card-image" />
61
- ) : (
62
- <div className="powerhouse-carousel__card-placeholder" />
63
- )}
64
- {onSale && saved != null ? (
65
- <span className="powerhouse-carousel__badge">Save {formatPrice(saved, currency)}</span>
66
- ) : null}
67
- {!inStock ? <span className="powerhouse-carousel__sold">Sold out</span> : null}
68
- </LocalizedLink>
60
+ <div className="powerhouse-carousel__media-wrap">
61
+ <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-carousel__card-media">
62
+ {product.thumbnail?.trim() ? (
63
+ <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-carousel__card-image" />
64
+ ) : (
65
+ <div className="powerhouse-carousel__card-placeholder" />
66
+ )}
67
+ {onSale && saved != null ? (
68
+ <span className="powerhouse-carousel__badge">Save {formatPrice(saved, currency)}</span>
69
+ ) : null}
70
+ {!inStock ? <span className="powerhouse-carousel__sold">Sold out</span> : null}
71
+ </LocalizedLink>
72
+ <WishlistButton
73
+ productId={product.id}
74
+ countryCode={countryCode}
75
+ wishlistProductIds={wishlistProductIds}
76
+ />
77
+ </div>
69
78
 
70
79
  <div className="powerhouse-carousel__card-info">
71
80
  {price != null ? (
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 products={list} region={displayRegion ?? undefined} countryCode={countryCode} />
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
+ }