@pradip1995/segment-powerhouse-shop 0.1.4 → 0.1.5

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-shop",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@pradip1995/commerce-core": "^4.0.0",
27
+ "@pradip1995/segment-login-popup": "^0.1.2",
27
28
  "@pradip1995/segment-primitives": "^0.4.0"
28
29
  },
29
30
  "devDependencies": {
@@ -7,6 +7,7 @@ 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
  function stripHtml(value?: string | null) {
12
13
  if (!value) return ""
@@ -23,10 +24,12 @@ export default function ShopProductCard({
23
24
  product,
24
25
  countryCode,
25
26
  layout = "grid",
27
+ wishlistProductIds,
26
28
  }: {
27
29
  product: HttpTypes.StoreProduct
28
30
  countryCode: string
29
31
  layout?: "grid" | "list"
32
+ wishlistProductIds?: string[]
30
33
  }) {
31
34
  const router = useRouter()
32
35
  const [adding, setAdding] = useState(false)
@@ -110,17 +113,24 @@ export default function ShopProductCard({
110
113
 
111
114
  return (
112
115
  <article className={`powerhouse-shop__card${onSale ? " is-sale" : ""}${isList ? " is-list-card" : ""}`}>
113
- <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-shop__media" tabIndex={-1}>
114
- {product.thumbnail?.trim() ? (
115
- <img src={product.thumbnail.trim()} alt={product.title || ""} />
116
- ) : (
117
- <div className="powerhouse-shop__placeholder" />
118
- )}
119
- {onSale && saved != null ? (
120
- <span className="powerhouse-shop__badge">Save {formatPrice(saved, currency)}</span>
121
- ) : null}
122
- {!inStock && !isList ? <span className="powerhouse-shop__sold">Sold out</span> : null}
123
- </LocalizedLink>
116
+ <div className="powerhouse-shop__media-wrap">
117
+ <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-shop__media" tabIndex={-1}>
118
+ {product.thumbnail?.trim() ? (
119
+ <img src={product.thumbnail.trim()} alt={product.title || ""} />
120
+ ) : (
121
+ <div className="powerhouse-shop__placeholder" />
122
+ )}
123
+ {onSale && saved != null ? (
124
+ <span className="powerhouse-shop__badge">Save {formatPrice(saved, currency)}</span>
125
+ ) : null}
126
+ {!inStock && !isList ? <span className="powerhouse-shop__sold">Sold out</span> : null}
127
+ </LocalizedLink>
128
+ <WishlistButton
129
+ productId={product.id}
130
+ countryCode={countryCode}
131
+ wishlistProductIds={wishlistProductIds}
132
+ />
133
+ </div>
124
134
 
125
135
  {isList ? (
126
136
  <>
package/src/shop.css CHANGED
@@ -458,7 +458,7 @@ html.powerhouse-filters-open body {
458
458
  border: 1px solid #dddddd;
459
459
  }
460
460
 
461
- .powerhouse-shop__grid.is-list .powerhouse-shop__media {
461
+ .powerhouse-shop__grid.is-list .powerhouse-shop__media-wrap {
462
462
  grid-row: 1 / 3;
463
463
  }
464
464
 
@@ -557,6 +557,10 @@ html.powerhouse-filters-open body {
557
557
  background: #fff;
558
558
  }
559
559
 
560
+ .powerhouse-shop__media-wrap {
561
+ position: relative;
562
+ }
563
+
560
564
  .powerhouse-shop__media {
561
565
  position: relative;
562
566
  display: block;
@@ -566,6 +570,51 @@ html.powerhouse-filters-open body {
566
570
  aspect-ratio: 1;
567
571
  }
568
572
 
573
+ .powerhouse-shop__wishlist {
574
+ position: absolute;
575
+ top: 0.5rem;
576
+ right: 0.5rem;
577
+ z-index: 2;
578
+ display: inline-flex;
579
+ align-items: center;
580
+ justify-content: center;
581
+ width: 2.25rem;
582
+ height: 2.25rem;
583
+ padding: 0;
584
+ border: 1px solid rgba(29, 29, 29, 0.12);
585
+ background: rgba(255, 255, 255, 0.94);
586
+ color: #1d1d1d;
587
+ cursor: pointer;
588
+ transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
589
+ }
590
+
591
+ .powerhouse-shop__wishlist:hover {
592
+ border-color: #1d1d1d;
593
+ }
594
+
595
+ .powerhouse-shop__wishlist.is-active {
596
+ color: #c1272d;
597
+ border-color: #c1272d;
598
+ }
599
+
600
+ .powerhouse-shop__wishlist.is-loading {
601
+ opacity: 0.55;
602
+ cursor: wait;
603
+ }
604
+
605
+ .powerhouse-shop__wishlist-icon {
606
+ width: 1.05rem;
607
+ height: 1.05rem;
608
+ fill: none;
609
+ stroke: currentColor;
610
+ stroke-width: 1.75;
611
+ stroke-linejoin: round;
612
+ }
613
+
614
+ .powerhouse-shop__wishlist.is-active .powerhouse-shop__wishlist-icon {
615
+ fill: currentColor;
616
+ }
617
+
569
618
  .powerhouse-shop__media img,
570
619
  .powerhouse-shop__placeholder {
571
620
  position: absolute;
@@ -592,8 +641,10 @@ html.powerhouse-filters-open body {
592
641
  }
593
642
 
594
643
  .powerhouse-shop__sold {
595
- left: auto;
596
- right: 0.5rem;
644
+ left: 0.5rem;
645
+ right: auto;
646
+ top: auto;
647
+ bottom: 0.5rem;
597
648
  background: #1d1d1d;
598
649
  color: #fff;
599
650
  }
@@ -857,7 +908,7 @@ html.powerhouse-filters-open body {
857
908
  padding: 1.25rem;
858
909
  }
859
910
 
860
- .powerhouse-shop__grid.is-list .powerhouse-shop__media {
911
+ .powerhouse-shop__grid.is-list .powerhouse-shop__media-wrap {
861
912
  grid-row: auto;
862
913
  }
863
914
 
package/src/shop.tsx CHANGED
@@ -620,6 +620,7 @@ export default function PowerhouseShop(props: PowerhouseShopProps) {
620
620
  product={product}
621
621
  countryCode={countryCode}
622
622
  layout={view}
623
+ wishlistProductIds={props.wishlistProductIds}
623
624
  />
624
625
  ))}
625
626
  </div>
package/src/types.ts CHANGED
@@ -25,6 +25,7 @@ export type PowerhouseShopProps = {
25
25
  basePath?: string
26
26
  bannerImage?: string
27
27
  bannerText?: string
28
+ wishlistProductIds?: string[]
28
29
  }
29
30
 
30
31
  export const PAGE_SIZE = 12
@@ -0,0 +1,94 @@
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-shop__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
+ return
66
+ }
67
+ } finally {
68
+ setLoading(false)
69
+ }
70
+ }
71
+
72
+ return (
73
+ <>
74
+ <button
75
+ type="button"
76
+ className={`${className}${wishlisted ? " is-active" : ""}${loading ? " is-loading" : ""}`}
77
+ onClick={handleToggle}
78
+ disabled={loading || !productId}
79
+ aria-label={wishlisted ? "Remove from wishlist" : "Add to wishlist"}
80
+ aria-pressed={wishlisted}
81
+ >
82
+ <svg viewBox="0 0 24 24" aria-hidden className={`${className}-icon`}>
83
+ <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" />
84
+ </svg>
85
+ </button>
86
+ <LoginRequiredModal
87
+ isOpen={loginRequired}
88
+ onClose={() => setLoginRequired(false)}
89
+ countryCode={countryCode}
90
+ message="Please log in to save items to your wishlist."
91
+ />
92
+ </>
93
+ )
94
+ }