@pradip1995/segment-powerhouse-deals 0.1.1 → 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-deals",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,6 +25,7 @@
25
25
  "dependencies": {
26
26
  "@pradip1995/commerce-core": "^4.0.0",
27
27
  "@pradip1995/medusa-connector": "^0.2.0",
28
+ "@pradip1995/segment-login-popup": "^0.1.2",
28
29
  "@pradip1995/segment-primitives": "^0.4.0"
29
30
  },
30
31
  "devDependencies": {
package/src/deal-card.tsx CHANGED
@@ -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 DealCard({ product, region, countryCode }: Props) {
19
+ export default function DealCard({ product, region, countryCode, wishlistProductIds }: Props) {
18
20
  const router = useRouter()
19
21
  const [adding, setAdding] = useState(false)
20
22
  const variant = product.variants?.[0]
@@ -24,12 +26,17 @@ export default function DealCard({ product, region, countryCode }: Props) {
24
26
  const onSale = originalPrice != null && price != null && originalPrice > price
25
27
  const saved = onSale && originalPrice != null && price != null ? originalPrice - price : null
26
28
  const inventory = variant?.inventory_quantity
29
+ const inStock =
30
+ variant?.manage_inventory === false ||
31
+ variant?.allow_backorder === true ||
32
+ (inventory ?? 0) > 0
27
33
  const href = `/products/${product.handle}`
34
+ const hasOptions = (product.variants?.length ?? 0) > 1
28
35
 
29
36
  const handleAdd = async (event: React.MouseEvent) => {
30
37
  event.preventDefault()
31
38
  event.stopPropagation()
32
- if (!variant?.id || adding) return
39
+ if (!variant?.id || adding || !inStock || hasOptions) return
33
40
  setAdding(true)
34
41
  try {
35
42
  await addToCart({
@@ -50,16 +57,24 @@ export default function DealCard({ product, region, countryCode }: Props) {
50
57
 
51
58
  return (
52
59
  <article className={`powerhouse-deals__card${onSale ? " is-sale" : ""}`}>
53
- <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__card-media">
54
- {product.thumbnail?.trim() ? (
55
- <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-deals__card-image" />
56
- ) : (
57
- <div className="powerhouse-deals__card-placeholder" />
58
- )}
59
- {onSale && saved != null ? (
60
- <span className="powerhouse-deals__badge">Save {formatPrice(saved, currency)}</span>
61
- ) : null}
62
- </LocalizedLink>
60
+ <div className="powerhouse-deals__media-wrap">
61
+ <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__card-media">
62
+ {product.thumbnail?.trim() ? (
63
+ <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-deals__card-image" />
64
+ ) : (
65
+ <div className="powerhouse-deals__card-placeholder" />
66
+ )}
67
+ {onSale && saved != null ? (
68
+ <span className="powerhouse-deals__badge">Save {formatPrice(saved, currency)}</span>
69
+ ) : null}
70
+ {!inStock ? <span className="powerhouse-deals__sold">Sold out</span> : null}
71
+ </LocalizedLink>
72
+ <WishlistButton
73
+ productId={product.id}
74
+ countryCode={countryCode}
75
+ wishlistProductIds={wishlistProductIds}
76
+ />
77
+ </div>
63
78
 
64
79
  <div className="powerhouse-deals__card-info">
65
80
  {price != null ? (
@@ -79,21 +94,30 @@ export default function DealCard({ product, region, countryCode }: Props) {
79
94
 
80
95
  {inventory != null && inventory > 0 && inventory <= 5 ? (
81
96
  <p className="powerhouse-deals__stock">Only {inventory} left!</p>
82
- ) : null}
97
+ ) : !inStock ? (
98
+ <p className="powerhouse-deals__stock is-out">Out of stock</p>
99
+ ) : (
100
+ <p className="powerhouse-deals__stock">in stock</p>
101
+ )}
83
102
 
84
103
  <div className="powerhouse-deals__actions">
85
104
  <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__quickshop">
86
105
  Quick shop
87
106
  </LocalizedLink>
88
- <button
89
- type="button"
90
- className="powerhouse-deals__atc"
91
- onClick={handleAdd}
92
- disabled={adding || !variant?.id}
93
- aria-label="Add to cart"
94
- >
95
- {adding ? "Adding…" : "Add to cart"}
96
- </button>
107
+ {hasOptions ? (
108
+ <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__atc">
109
+ Choose options
110
+ </LocalizedLink>
111
+ ) : (
112
+ <button
113
+ type="button"
114
+ className="powerhouse-deals__atc"
115
+ onClick={handleAdd}
116
+ disabled={adding || !variant?.id || !inStock}
117
+ >
118
+ {adding ? "Adding…" : !inStock ? "Sold out" : "Add to cart"}
119
+ </button>
120
+ )}
97
121
  </div>
98
122
  </div>
99
123
  </article>
package/src/deals.css CHANGED
@@ -38,6 +38,12 @@
38
38
  height: 100%;
39
39
  object-fit: cover;
40
40
  object-position: center;
41
+ transform: scale(1);
42
+ transition: transform 0.5s cubic-bezier(0, 0, 0.2, 1);
43
+ }
44
+
45
+ .powerhouse-deals__promo:hover .powerhouse-deals__promo-image {
46
+ transform: scale(1.05);
41
47
  }
42
48
 
43
49
  .powerhouse-deals__promo-link {
@@ -117,16 +123,18 @@
117
123
  }
118
124
 
119
125
  .powerhouse-deals__card {
126
+ position: relative;
127
+ z-index: 1;
120
128
  display: flex;
121
129
  flex-direction: column;
122
130
  height: 100%;
123
131
  padding: 0.6875rem;
132
+ border: 1px solid #dddddd;
124
133
  background: #fff;
125
- transition: box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1);
126
134
  }
127
135
 
128
- .powerhouse-deals__card:hover {
129
- box-shadow: 0 2px 8px #80808033;
136
+ .powerhouse-deals__media-wrap {
137
+ position: relative;
130
138
  }
131
139
 
132
140
  .powerhouse-deals__card-media {
@@ -137,12 +145,63 @@
137
145
  background: #f3f3f3;
138
146
  }
139
147
 
148
+ .powerhouse-deals__wishlist {
149
+ position: absolute;
150
+ top: 0.5rem;
151
+ right: 0.5rem;
152
+ z-index: 2;
153
+ display: inline-flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ width: 2.25rem;
157
+ height: 2.25rem;
158
+ padding: 0;
159
+ border: 1px solid rgba(29, 29, 29, 0.12);
160
+ background: rgba(255, 255, 255, 0.94);
161
+ color: #1d1d1d;
162
+ cursor: pointer;
163
+ transition: color 0.2s ease, border-color 0.2s ease;
164
+ }
165
+
166
+ .powerhouse-deals__wishlist:hover {
167
+ border-color: #1d1d1d;
168
+ }
169
+
170
+ .powerhouse-deals__wishlist.is-active {
171
+ color: #c1272d;
172
+ border-color: #c1272d;
173
+ }
174
+
175
+ .powerhouse-deals__wishlist.is-loading {
176
+ opacity: 0.55;
177
+ cursor: wait;
178
+ }
179
+
180
+ .powerhouse-deals__wishlist-icon {
181
+ width: 1.05rem;
182
+ height: 1.05rem;
183
+ fill: none;
184
+ stroke: currentColor;
185
+ stroke-width: 1.75;
186
+ stroke-linejoin: round;
187
+ }
188
+
189
+ .powerhouse-deals__wishlist.is-active .powerhouse-deals__wishlist-icon {
190
+ fill: currentColor;
191
+ }
192
+
140
193
  .powerhouse-deals__card-image,
141
194
  .powerhouse-deals__card-placeholder {
142
195
  width: 100%;
143
196
  height: 100%;
144
197
  object-fit: cover;
145
198
  display: block;
199
+ transform: scale(1);
200
+ transition: transform 0.5s cubic-bezier(0, 0, 0.2, 1);
201
+ }
202
+
203
+ .powerhouse-deals__card:hover .powerhouse-deals__card-image {
204
+ transform: scale(1.05);
146
205
  }
147
206
 
148
207
  .powerhouse-deals__badge {
@@ -158,6 +217,19 @@
158
217
  line-height: 1.2;
159
218
  }
160
219
 
220
+ .powerhouse-deals__sold {
221
+ position: absolute;
222
+ bottom: 0.6rem;
223
+ left: 0.6rem;
224
+ padding: 0.2rem 0.45rem;
225
+ background: #1d1d1d;
226
+ color: #fff;
227
+ font-family: var(--font-sans, inherit);
228
+ font-size: 0.75rem;
229
+ font-weight: 600;
230
+ line-height: 1.2;
231
+ }
232
+
161
233
  .powerhouse-deals__card-info {
162
234
  display: flex;
163
235
  flex-direction: column;
@@ -167,6 +239,7 @@
167
239
 
168
240
  .powerhouse-deals__price {
169
241
  margin-top: 0.25rem;
242
+ min-height: 2.7rem;
170
243
  }
171
244
 
172
245
  .powerhouse-deals__price-compare {
@@ -190,7 +263,12 @@
190
263
  }
191
264
 
192
265
  .powerhouse-deals__card-title {
266
+ display: -webkit-box;
267
+ -webkit-box-orient: vertical;
268
+ -webkit-line-clamp: 2;
269
+ overflow: hidden;
193
270
  margin: 0.625rem 0 0;
271
+ min-height: calc(1em * 1.3125 * 2);
194
272
  font-family: var(--font-sans, inherit);
195
273
  font-size: 1rem;
196
274
  font-weight: 300;
@@ -204,31 +282,41 @@
204
282
 
205
283
  .powerhouse-deals__stock {
206
284
  margin: 0.5rem 0 0;
285
+ min-height: 1.2em;
207
286
  color: #688600;
208
287
  font-size: 0.875rem;
209
288
  }
210
289
 
290
+ .powerhouse-deals__stock.is-out {
291
+ color: #6e6e6e;
292
+ }
293
+
211
294
  .powerhouse-deals__actions {
212
295
  display: flex;
213
- flex-wrap: wrap;
214
- gap: 0.5rem;
296
+ flex-direction: column;
297
+ flex-wrap: nowrap;
298
+ gap: 0.4rem;
215
299
  margin-top: auto;
216
300
  padding-top: 0.75rem;
217
301
  }
218
302
 
219
303
  .powerhouse-deals__quickshop,
220
304
  .powerhouse-deals__atc {
221
- display: inline-flex;
305
+ display: flex;
222
306
  align-items: center;
223
307
  justify-content: center;
308
+ width: 100%;
224
309
  min-height: 2.5rem;
225
- padding: 0.55rem 0.9rem;
310
+ padding: 0.5rem 0.45rem;
226
311
  border-radius: 2px;
312
+ box-sizing: border-box;
227
313
  font-family: var(--font-sans, inherit);
228
- font-size: 0.875rem;
229
- font-weight: 500;
230
- letter-spacing: 0.04em;
314
+ font-size: 0.8125rem;
315
+ font-weight: 600;
316
+ letter-spacing: 0;
231
317
  text-decoration: none;
318
+ text-align: center;
319
+ white-space: nowrap;
232
320
  cursor: pointer;
233
321
  }
234
322
 
@@ -254,7 +342,7 @@
254
342
 
255
343
  .powerhouse-deals__atc:disabled {
256
344
  opacity: 0.65;
257
- cursor: wait;
345
+ cursor: not-allowed;
258
346
  }
259
347
 
260
348
  @media screen and (min-width: 720px) {
@@ -281,9 +369,52 @@
281
369
  padding: 1.75rem;
282
370
  }
283
371
 
372
+ .powerhouse-deals__grid {
373
+ align-items: stretch;
374
+ overflow: visible;
375
+ }
376
+
284
377
  .powerhouse-deals__card {
285
378
  padding: 1rem;
286
379
  }
380
+
381
+ .powerhouse-deals__card:hover,
382
+ .powerhouse-deals__card:focus-within {
383
+ z-index: 8;
384
+ box-shadow: 0 6px 18px rgba(29, 29, 29, 0.12);
385
+ }
386
+
387
+ .powerhouse-deals__actions {
388
+ position: absolute;
389
+ left: -1px;
390
+ right: -1px;
391
+ top: calc(100% - 1px);
392
+ flex-direction: row;
393
+ margin-top: 0;
394
+ padding: 0.75rem 1rem 1rem;
395
+ border: 1px solid #dddddd;
396
+ border-top: 0;
397
+ background: #fff;
398
+ box-sizing: border-box;
399
+ opacity: 0;
400
+ visibility: hidden;
401
+ pointer-events: none;
402
+ transition: opacity 0.2s ease, visibility 0.2s ease;
403
+ }
404
+
405
+ .powerhouse-deals__quickshop,
406
+ .powerhouse-deals__atc {
407
+ flex: 1 1 0;
408
+ width: auto;
409
+ min-width: 0;
410
+ }
411
+
412
+ .powerhouse-deals__card:hover .powerhouse-deals__actions,
413
+ .powerhouse-deals__card:focus-within .powerhouse-deals__actions {
414
+ opacity: 1;
415
+ visibility: visible;
416
+ pointer-events: auto;
417
+ }
287
418
  }
288
419
 
289
420
  @media screen and (min-width: 1080px) {
@@ -301,17 +432,6 @@
301
432
  .powerhouse-deals__promo-title {
302
433
  font-size: 2.55rem;
303
434
  }
304
-
305
- .powerhouse-deals__actions {
306
- opacity: 0;
307
- pointer-events: none;
308
- transition: opacity 0.2s ease;
309
- }
310
-
311
- .powerhouse-deals__card:hover .powerhouse-deals__actions {
312
- opacity: 1;
313
- pointer-events: auto;
314
- }
315
435
  }
316
436
 
317
437
  @media screen and (max-width: 859px) {
package/src/segment.tsx CHANGED
@@ -24,6 +24,7 @@ export default async function PowerhouseDeals({
24
24
  promoLink = DEFAULT_PROMO_LINK,
25
25
  promoImage = DEFAULT_PROMO_IMAGE,
26
26
  emptyMessage = DEFAULT_EMPTY,
27
+ wishlistProductIds,
27
28
  }: PowerhouseDealsProps) {
28
29
  const countryCode =
29
30
  countryCodeProp?.toLowerCase() ||
@@ -95,6 +96,7 @@ export default async function PowerhouseDeals({
95
96
  product={product}
96
97
  region={displayRegion ?? undefined}
97
98
  countryCode={countryCode}
99
+ wishlistProductIds={wishlistProductIds}
98
100
  />
99
101
  ))
100
102
  )}
@@ -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-deals__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
+ }