@pradip1995/segment-powerhouse-shop 0.1.3 → 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.3",
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
 
@@ -544,11 +544,21 @@ html.powerhouse-filters-open body {
544
544
  }
545
545
 
546
546
  .powerhouse-shop__card {
547
+ position: relative;
548
+ z-index: 1;
547
549
  display: flex;
548
550
  flex-direction: column;
549
551
  height: 100%;
550
552
  min-width: 0;
551
553
  max-width: 100%;
554
+ padding: 0.6875rem;
555
+ border: 1px solid #dddddd;
556
+ box-sizing: border-box;
557
+ background: #fff;
558
+ }
559
+
560
+ .powerhouse-shop__media-wrap {
561
+ position: relative;
552
562
  }
553
563
 
554
564
  .powerhouse-shop__media {
@@ -560,6 +570,51 @@ html.powerhouse-filters-open body {
560
570
  aspect-ratio: 1;
561
571
  }
562
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
+
563
618
  .powerhouse-shop__media img,
564
619
  .powerhouse-shop__placeholder {
565
620
  position: absolute;
@@ -586,8 +641,10 @@ html.powerhouse-filters-open body {
586
641
  }
587
642
 
588
643
  .powerhouse-shop__sold {
589
- left: auto;
590
- right: 0.5rem;
644
+ left: 0.5rem;
645
+ right: auto;
646
+ top: auto;
647
+ bottom: 0.5rem;
591
648
  background: #1d1d1d;
592
649
  color: #fff;
593
650
  }
@@ -851,7 +908,7 @@ html.powerhouse-filters-open body {
851
908
  padding: 1.25rem;
852
909
  }
853
910
 
854
- .powerhouse-shop__grid.is-list .powerhouse-shop__media {
911
+ .powerhouse-shop__grid.is-list .powerhouse-shop__media-wrap {
855
912
  grid-row: auto;
856
913
  }
857
914
 
@@ -921,9 +978,17 @@ html.powerhouse-filters-open body {
921
978
  }
922
979
 
923
980
  .powerhouse-shop__grid {
981
+ align-items: stretch;
982
+ overflow: visible;
924
983
  grid-template-columns: repeat(3, minmax(0, 1fr));
925
984
  }
926
985
 
986
+ .powerhouse-shop__grid:not(.is-list) .powerhouse-shop__card:hover,
987
+ .powerhouse-shop__grid:not(.is-list) .powerhouse-shop__card:focus-within {
988
+ z-index: 8;
989
+ box-shadow: 0 6px 18px rgba(29, 29, 29, 0.12);
990
+ }
991
+
927
992
  .powerhouse-shop__grid.is-list {
928
993
  grid-template-columns: 1fr;
929
994
  }
@@ -946,11 +1011,22 @@ html.powerhouse-filters-open body {
946
1011
  width: 100%;
947
1012
  }
948
1013
 
949
- .powerhouse-shop__actions {
1014
+ .powerhouse-shop__grid:not(.is-list) .powerhouse-shop__actions {
1015
+ position: absolute;
1016
+ left: -1px;
1017
+ right: -1px;
1018
+ top: calc(100% - 1px);
950
1019
  flex-direction: row;
1020
+ margin-top: 0;
1021
+ padding: 0.75rem 0.6875rem 0.6875rem;
1022
+ border: 1px solid #dddddd;
1023
+ border-top: 0;
1024
+ background: #fff;
1025
+ box-sizing: border-box;
951
1026
  opacity: 0;
1027
+ visibility: hidden;
952
1028
  pointer-events: none;
953
- transition: opacity 0.2s ease;
1029
+ transition: opacity 0.2s ease, visibility 0.2s ease;
954
1030
  }
955
1031
 
956
1032
  .powerhouse-shop__quickshop,
@@ -960,11 +1036,20 @@ html.powerhouse-filters-open body {
960
1036
  min-width: 0;
961
1037
  }
962
1038
 
963
- .powerhouse-shop__card:hover .powerhouse-shop__actions,
964
- .powerhouse-shop__card:focus-within .powerhouse-shop__actions,
1039
+ .powerhouse-shop__grid:not(.is-list) .powerhouse-shop__card:hover .powerhouse-shop__actions,
1040
+ .powerhouse-shop__grid:not(.is-list) .powerhouse-shop__card:focus-within .powerhouse-shop__actions {
1041
+ opacity: 1;
1042
+ visibility: visible;
1043
+ pointer-events: auto;
1044
+ }
1045
+
965
1046
  .powerhouse-shop__grid.is-list .powerhouse-shop__actions {
1047
+ position: static;
966
1048
  opacity: 1;
1049
+ visibility: visible;
967
1050
  pointer-events: auto;
1051
+ border: 0;
1052
+ background: transparent;
968
1053
  }
969
1054
  }
970
1055
 
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
+ }