@pradip1995/segment-powerhouse-shop 0.1.4 → 0.1.6
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 +2 -1
- package/src/product-card.tsx +58 -18
- package/src/shop.css +61 -4
- package/src/shop.tsx +1 -0
- package/src/types.ts +1 -0
- package/src/wishlist-button.tsx +94 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-powerhouse-shop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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": {
|
package/src/product-card.tsx
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, type MouseEvent } from "react"
|
|
4
4
|
import { useRouter } from "next/navigation"
|
|
5
|
+
import { buyNow } from "@pradip1995/commerce-core/client/actions/cart"
|
|
5
6
|
import { addToCart } from "./add-to-cart"
|
|
6
7
|
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
7
8
|
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
8
9
|
import { isNextRedirect } from "@pradip1995/segment-primitives/is-next-redirect"
|
|
9
10
|
import type { HttpTypes } from "@medusajs/types"
|
|
11
|
+
import WishlistButton from "./wishlist-button"
|
|
10
12
|
|
|
11
13
|
function stripHtml(value?: string | null) {
|
|
12
14
|
if (!value) return ""
|
|
@@ -23,13 +25,16 @@ export default function ShopProductCard({
|
|
|
23
25
|
product,
|
|
24
26
|
countryCode,
|
|
25
27
|
layout = "grid",
|
|
28
|
+
wishlistProductIds,
|
|
26
29
|
}: {
|
|
27
30
|
product: HttpTypes.StoreProduct
|
|
28
31
|
countryCode: string
|
|
29
32
|
layout?: "grid" | "list"
|
|
33
|
+
wishlistProductIds?: string[]
|
|
30
34
|
}) {
|
|
31
35
|
const router = useRouter()
|
|
32
36
|
const [adding, setAdding] = useState(false)
|
|
37
|
+
const [buying, setBuying] = useState(false)
|
|
33
38
|
const variants = product.variants ?? []
|
|
34
39
|
const variant = variants[0]
|
|
35
40
|
const price = variant?.calculated_price?.calculated_amount
|
|
@@ -47,11 +52,12 @@ export default function ShopProductCard({
|
|
|
47
52
|
const hasOptions = variants.length > 1
|
|
48
53
|
const copy = snippet(product.description, layout === "list" ? 220 : 140)
|
|
49
54
|
const isList = layout === "list"
|
|
55
|
+
const busy = adding || buying
|
|
50
56
|
|
|
51
57
|
const handleAdd = async (event: MouseEvent) => {
|
|
52
58
|
event.preventDefault()
|
|
53
59
|
event.stopPropagation()
|
|
54
|
-
if (!variant?.id ||
|
|
60
|
+
if (!variant?.id || busy || !inStock || hasOptions) return
|
|
55
61
|
setAdding(true)
|
|
56
62
|
try {
|
|
57
63
|
await addToCart({
|
|
@@ -70,6 +76,26 @@ export default function ShopProductCard({
|
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
const handleBuyNow = async (event: MouseEvent) => {
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
event.stopPropagation()
|
|
82
|
+
if (!variant?.id || busy || !inStock) return
|
|
83
|
+
setBuying(true)
|
|
84
|
+
try {
|
|
85
|
+
await buyNow({
|
|
86
|
+
variantId: variant.id,
|
|
87
|
+
quantity: 1,
|
|
88
|
+
countryCode,
|
|
89
|
+
})
|
|
90
|
+
router.refresh()
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (isNextRedirect(err)) throw err
|
|
93
|
+
const message = err instanceof Error ? err.message : "Buy now failed"
|
|
94
|
+
window.dispatchEvent(new CustomEvent("cart:item-added", { detail: { message, variant: "error" } }))
|
|
95
|
+
setBuying(false)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
const priceBlock =
|
|
74
100
|
price != null ? (
|
|
75
101
|
<div className="powerhouse-shop__price">
|
|
@@ -80,13 +106,20 @@ export default function ShopProductCard({
|
|
|
80
106
|
</div>
|
|
81
107
|
) : null
|
|
82
108
|
|
|
109
|
+
const buyNowControl = (
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
className="powerhouse-shop__quickshop"
|
|
113
|
+
onClick={handleBuyNow}
|
|
114
|
+
disabled={busy || !variant?.id}
|
|
115
|
+
>
|
|
116
|
+
{buying ? "Processing…" : "Buy now"}
|
|
117
|
+
</button>
|
|
118
|
+
)
|
|
119
|
+
|
|
83
120
|
const actionBlock = (
|
|
84
121
|
<div className="powerhouse-shop__actions">
|
|
85
|
-
{inStock ?
|
|
86
|
-
<LocalizedLink href={href} countryCode={countryCode} className="powerhouse-shop__quickshop">
|
|
87
|
-
Quick shop
|
|
88
|
-
</LocalizedLink>
|
|
89
|
-
) : null}
|
|
122
|
+
{inStock ? buyNowControl : null}
|
|
90
123
|
{!inStock ? (
|
|
91
124
|
<button type="button" className="powerhouse-shop__atc" disabled>
|
|
92
125
|
Sold out
|
|
@@ -100,7 +133,7 @@ export default function ShopProductCard({
|
|
|
100
133
|
type="button"
|
|
101
134
|
className="powerhouse-shop__atc"
|
|
102
135
|
onClick={handleAdd}
|
|
103
|
-
disabled={
|
|
136
|
+
disabled={busy || !variant?.id}
|
|
104
137
|
>
|
|
105
138
|
{adding ? "Adding…" : "Add to cart"}
|
|
106
139
|
</button>
|
|
@@ -110,17 +143,24 @@ export default function ShopProductCard({
|
|
|
110
143
|
|
|
111
144
|
return (
|
|
112
145
|
<article className={`powerhouse-shop__card${onSale ? " is-sale" : ""}${isList ? " is-list-card" : ""}`}>
|
|
113
|
-
<
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
<div className="powerhouse-shop__media-wrap">
|
|
147
|
+
<LocalizedLink href={href} countryCode={countryCode} className="powerhouse-shop__media" tabIndex={-1}>
|
|
148
|
+
{product.thumbnail?.trim() ? (
|
|
149
|
+
<img src={product.thumbnail.trim()} alt={product.title || ""} />
|
|
150
|
+
) : (
|
|
151
|
+
<div className="powerhouse-shop__placeholder" />
|
|
152
|
+
)}
|
|
153
|
+
{onSale && saved != null ? (
|
|
154
|
+
<span className="powerhouse-shop__badge">Save {formatPrice(saved, currency)}</span>
|
|
155
|
+
) : null}
|
|
156
|
+
{!inStock && !isList ? <span className="powerhouse-shop__sold">Sold out</span> : null}
|
|
157
|
+
</LocalizedLink>
|
|
158
|
+
<WishlistButton
|
|
159
|
+
productId={product.id}
|
|
160
|
+
countryCode={countryCode}
|
|
161
|
+
wishlistProductIds={wishlistProductIds}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
124
164
|
|
|
125
165
|
{isList ? (
|
|
126
166
|
<>
|
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:
|
|
596
|
-
right:
|
|
644
|
+
left: 0.5rem;
|
|
645
|
+
right: auto;
|
|
646
|
+
top: auto;
|
|
647
|
+
bottom: 0.5rem;
|
|
597
648
|
background: #1d1d1d;
|
|
598
649
|
color: #fff;
|
|
599
650
|
}
|
|
@@ -699,6 +750,12 @@ html.powerhouse-filters-open body {
|
|
|
699
750
|
border: 1px solid #f2f682;
|
|
700
751
|
background: #f1eeae;
|
|
701
752
|
color: #1d1d1d;
|
|
753
|
+
cursor: pointer;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.powerhouse-shop__quickshop:disabled {
|
|
757
|
+
opacity: 0.55;
|
|
758
|
+
cursor: not-allowed;
|
|
702
759
|
}
|
|
703
760
|
|
|
704
761
|
.powerhouse-shop__atc {
|
|
@@ -857,7 +914,7 @@ html.powerhouse-filters-open body {
|
|
|
857
914
|
padding: 1.25rem;
|
|
858
915
|
}
|
|
859
916
|
|
|
860
|
-
.powerhouse-shop__grid.is-list .powerhouse-shop__media {
|
|
917
|
+
.powerhouse-shop__grid.is-list .powerhouse-shop__media-wrap {
|
|
861
918
|
grid-row: auto;
|
|
862
919
|
}
|
|
863
920
|
|
package/src/shop.tsx
CHANGED
package/src/types.ts
CHANGED
|
@@ -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
|
+
}
|