@pradip1995/segment-powerhouse-deals 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pradip1995/segment-powerhouse-deals",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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
@@ -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 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)
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 DealCard({ 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 || adding || !inStock || hasOptions) return
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: countryCode.toLowerCase(),
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 DealCard({ 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-deals__card${onSale ? " is-sale" : ""}`}>
58
- <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__card-media">
59
- {product.thumbnail?.trim() ? (
60
- <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-deals__card-image" />
61
- ) : (
62
- <div className="powerhouse-deals__card-placeholder" />
63
- )}
64
- {onSale && saved != null ? (
65
- <span className="powerhouse-deals__badge">Save {formatPrice(saved, currency)}</span>
66
- ) : null}
67
- {!inStock ? <span className="powerhouse-deals__sold">Sold out</span> : null}
68
- </LocalizedLink>
83
+ <div className="powerhouse-deals__media-wrap">
84
+ <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__card-media">
85
+ {product.thumbnail?.trim() ? (
86
+ <img src={product.thumbnail.trim()} alt={product.title || ""} className="powerhouse-deals__card-image" />
87
+ ) : (
88
+ <div className="powerhouse-deals__card-placeholder" />
89
+ )}
90
+ {onSale && saved != null ? (
91
+ <span className="powerhouse-deals__badge">Save {formatPrice(saved, currency)}</span>
92
+ ) : null}
93
+ {!inStock ? <span className="powerhouse-deals__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-deals__card-info">
71
103
  {price != null ? (
@@ -92,9 +124,16 @@ export default function DealCard({ product, region, countryCode }: Props) {
92
124
  )}
93
125
 
94
126
  <div className="powerhouse-deals__actions">
95
- <LocalizedLink href={href} countryCode={countryCode} className="powerhouse-deals__quickshop">
96
- Quick shop
97
- </LocalizedLink>
127
+ {inStock ? (
128
+ <button
129
+ type="button"
130
+ className="powerhouse-deals__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-deals__atc">
100
139
  Choose options
@@ -104,7 +143,7 @@ export default function DealCard({ product, region, countryCode }: Props) {
104
143
  type="button"
105
144
  className="powerhouse-deals__atc"
106
145
  onClick={handleAdd}
107
- disabled={adding || !variant?.id || !inStock}
146
+ disabled={busy || !variant?.id || !inStock}
108
147
  >
109
148
  {adding ? "Adding…" : !inStock ? "Sold out" : "Add to cart"}
110
149
  </button>
package/src/deals.css CHANGED
@@ -133,6 +133,10 @@
133
133
  background: #fff;
134
134
  }
135
135
 
136
+ .powerhouse-deals__media-wrap {
137
+ position: relative;
138
+ }
139
+
136
140
  .powerhouse-deals__card-media {
137
141
  position: relative;
138
142
  display: block;
@@ -141,6 +145,51 @@
141
145
  background: #f3f3f3;
142
146
  }
143
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
+
144
193
  .powerhouse-deals__card-image,
145
194
  .powerhouse-deals__card-placeholder {
146
195
  width: 100%;
@@ -275,9 +324,15 @@
275
324
  background: #f1eeae;
276
325
  border: 1px solid #f2f682;
277
326
  color: #1d1d1d;
327
+ cursor: pointer;
328
+ }
329
+
330
+ .powerhouse-deals__quickshop:disabled {
331
+ opacity: 0.55;
332
+ cursor: not-allowed;
278
333
  }
279
334
 
280
- .powerhouse-deals__quickshop:hover {
335
+ .powerhouse-deals__quickshop:hover:not(:disabled) {
281
336
  background: #f4f1be;
282
337
  }
283
338
 
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
+ }