@pradip1995/segment-product-card 0.3.1 → 0.3.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 +6 -4
- package/src/beauty-product-card.tsx +108 -0
- package/src/default-product-card.tsx +86 -0
- package/src/segment.css +225 -0
- package/src/segment.tsx +15 -72
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-product-card",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"sideEffects":
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"src/segment.css"
|
|
10
|
+
],
|
|
9
11
|
"files": [
|
|
10
12
|
"src"
|
|
11
13
|
],
|
|
@@ -23,8 +25,8 @@
|
|
|
23
25
|
"next": ">=15"
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
26
|
-
"@pradip1995/segment-
|
|
27
|
-
"@pradip1995/segment-
|
|
28
|
+
"@pradip1995/segment-primitives": "0.3.0",
|
|
29
|
+
"@pradip1995/segment-tokens": "0.3.2"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
32
|
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
4
|
+
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
5
|
+
import type { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import ProductCardActions from "./product-card-actions"
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
product: HttpTypes.StoreProduct
|
|
10
|
+
region?: HttpTypes.StoreRegion
|
|
11
|
+
countryCode?: string
|
|
12
|
+
rating?: { rating?: number }
|
|
13
|
+
showWishlist?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function productSubtitle(product: HttpTypes.StoreProduct) {
|
|
17
|
+
const category = product.categories?.[0]?.name?.trim()
|
|
18
|
+
if (category) return category
|
|
19
|
+
const collection = (product as HttpTypes.StoreProduct & { collection?: { title?: string } }).collection
|
|
20
|
+
?.title
|
|
21
|
+
return collection?.trim() || "Beauty"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function BeautyProductCard({
|
|
25
|
+
product,
|
|
26
|
+
region,
|
|
27
|
+
countryCode: countryCodeProp,
|
|
28
|
+
rating,
|
|
29
|
+
showWishlist = true,
|
|
30
|
+
}: Props) {
|
|
31
|
+
const variant = product.variants?.[0]
|
|
32
|
+
const price = variant?.calculated_price?.calculated_amount
|
|
33
|
+
const originalPrice = variant?.calculated_price?.original_amount
|
|
34
|
+
const currency = region?.currency_code || variant?.calculated_price?.currency_code || "inr"
|
|
35
|
+
const countryCode =
|
|
36
|
+
countryCodeProp?.toLowerCase() ??
|
|
37
|
+
region?.countries?.[0]?.iso_2?.toLowerCase() ??
|
|
38
|
+
process.env.NEXT_PUBLIC_DEFAULT_REGION?.toLowerCase() ??
|
|
39
|
+
"in"
|
|
40
|
+
|
|
41
|
+
const discountPct =
|
|
42
|
+
originalPrice && price && originalPrice > price
|
|
43
|
+
? Math.round(((originalPrice - price) / originalPrice) * 100)
|
|
44
|
+
: null
|
|
45
|
+
|
|
46
|
+
const subtitle = productSubtitle(product)
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<article
|
|
50
|
+
data-product-card-id={product.id}
|
|
51
|
+
className="beauty-product-card group flex flex-col w-full h-full relative"
|
|
52
|
+
>
|
|
53
|
+
<LocalizedLink
|
|
54
|
+
href={`/products/${product.handle}`}
|
|
55
|
+
className="beauty-product-card__link flex flex-col h-full"
|
|
56
|
+
>
|
|
57
|
+
<div className="beauty-product-card__media relative aspect-square bg-[#faf7f5] overflow-hidden">
|
|
58
|
+
{product.thumbnail?.trim() ? (
|
|
59
|
+
<img
|
|
60
|
+
src={product.thumbnail.trim()}
|
|
61
|
+
alt={product.title || ""}
|
|
62
|
+
className="beauty-product-card__image w-full h-full object-cover"
|
|
63
|
+
/>
|
|
64
|
+
) : (
|
|
65
|
+
<div className="beauty-product-card__placeholder w-full h-full" />
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{discountPct != null ? (
|
|
69
|
+
<span className="beauty-product-card__badge beauty-product-card__badge--sale">
|
|
70
|
+
{discountPct}% off
|
|
71
|
+
</span>
|
|
72
|
+
) : null}
|
|
73
|
+
|
|
74
|
+
{showWishlist && product.id ? (
|
|
75
|
+
<ProductCardActions product={product} countryCode={countryCode} />
|
|
76
|
+
) : null}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="beauty-product-card__body">
|
|
80
|
+
<p className="beauty-product-card__eyebrow">{subtitle}</p>
|
|
81
|
+
<h3 className="beauty-product-card__title">{product.title}</h3>
|
|
82
|
+
|
|
83
|
+
<div className="beauty-product-card__footer">
|
|
84
|
+
{rating?.rating != null && rating.rating > 0 ? (
|
|
85
|
+
<p className="beauty-product-card__rating" aria-label={`${rating.rating} out of 5 stars`}>
|
|
86
|
+
<span className="beauty-product-card__rating-stars" aria-hidden="true">
|
|
87
|
+
★
|
|
88
|
+
</span>
|
|
89
|
+
{rating.rating.toFixed(1)}
|
|
90
|
+
</p>
|
|
91
|
+
) : (
|
|
92
|
+
<span />
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{price != null ? (
|
|
96
|
+
<div className="beauty-product-card__prices">
|
|
97
|
+
<span className="beauty-product-card__price">{formatPrice(price, currency)}</span>
|
|
98
|
+
{originalPrice && price && originalPrice > price ? (
|
|
99
|
+
<span className="beauty-product-card__compare">{formatPrice(originalPrice, currency)}</span>
|
|
100
|
+
) : null}
|
|
101
|
+
</div>
|
|
102
|
+
) : null}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</LocalizedLink>
|
|
106
|
+
</article>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
4
|
+
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
5
|
+
import type { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import ProductCardActions from "./product-card-actions"
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
product: HttpTypes.StoreProduct
|
|
10
|
+
region?: HttpTypes.StoreRegion
|
|
11
|
+
countryCode?: string
|
|
12
|
+
rating?: { rating?: number }
|
|
13
|
+
showWishlist?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function DefaultProductCard({
|
|
17
|
+
product,
|
|
18
|
+
region,
|
|
19
|
+
countryCode: countryCodeProp,
|
|
20
|
+
rating,
|
|
21
|
+
showWishlist = true,
|
|
22
|
+
}: Props) {
|
|
23
|
+
const variant = product.variants?.[0]
|
|
24
|
+
const price = variant?.calculated_price?.calculated_amount
|
|
25
|
+
const originalPrice = variant?.calculated_price?.original_amount
|
|
26
|
+
const currency = region?.currency_code || variant?.calculated_price?.currency_code || "inr"
|
|
27
|
+
const countryCode =
|
|
28
|
+
countryCodeProp?.toLowerCase() ??
|
|
29
|
+
region?.countries?.[0]?.iso_2?.toLowerCase() ??
|
|
30
|
+
process.env.NEXT_PUBLIC_DEFAULT_REGION?.toLowerCase() ??
|
|
31
|
+
"in"
|
|
32
|
+
|
|
33
|
+
const discountPct =
|
|
34
|
+
originalPrice && price && originalPrice > price
|
|
35
|
+
? Math.round(((originalPrice - price) / originalPrice) * 100)
|
|
36
|
+
: null
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<article
|
|
40
|
+
data-product-card-id={product.id}
|
|
41
|
+
className="product-card group flex flex-col w-full h-full relative"
|
|
42
|
+
>
|
|
43
|
+
<LocalizedLink href={`/products/${product.handle}`} className="product-card__link flex flex-col h-full">
|
|
44
|
+
<div className="product-card__media relative aspect-[3/4] bg-surface-muted overflow-hidden rounded-lg">
|
|
45
|
+
{product.thumbnail?.trim() ? (
|
|
46
|
+
<img
|
|
47
|
+
src={product.thumbnail.trim()}
|
|
48
|
+
alt={product.title || ""}
|
|
49
|
+
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<div className="w-full h-full bg-surface-muted" />
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{showWishlist && product.id ? (
|
|
56
|
+
<ProductCardActions product={product} countryCode={countryCode} />
|
|
57
|
+
) : null}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="product-card__info mt-3 flex flex-col gap-1 min-w-0">
|
|
61
|
+
<h3 className="product-card__title text-sm font-medium text-heading group-hover:text-brand-accent transition-colors line-clamp-2">
|
|
62
|
+
{product.title}
|
|
63
|
+
</h3>
|
|
64
|
+
|
|
65
|
+
{rating?.rating != null && rating.rating > 0 ? (
|
|
66
|
+
<p className="product-card__rating text-xs text-muted">★ {rating.rating.toFixed(1)}</p>
|
|
67
|
+
) : null}
|
|
68
|
+
|
|
69
|
+
{price != null ? (
|
|
70
|
+
<div className="product-card__prices">
|
|
71
|
+
<span className="product-card__price">{formatPrice(price, currency)}</span>
|
|
72
|
+
{originalPrice && price && originalPrice > price ? (
|
|
73
|
+
<>
|
|
74
|
+
<span className="product-card__compare">{formatPrice(originalPrice, currency)}</span>
|
|
75
|
+
{discountPct != null ? (
|
|
76
|
+
<span className="product-card__discount">{discountPct}% OFF</span>
|
|
77
|
+
) : null}
|
|
78
|
+
</>
|
|
79
|
+
) : null}
|
|
80
|
+
</div>
|
|
81
|
+
) : null}
|
|
82
|
+
</div>
|
|
83
|
+
</LocalizedLink>
|
|
84
|
+
</article>
|
|
85
|
+
)
|
|
86
|
+
}
|
package/src/segment.css
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/* Beauty product card — Lumière cosmetics */
|
|
2
|
+
|
|
3
|
+
.beauty-product-card {
|
|
4
|
+
background: #ffffff;
|
|
5
|
+
border: 1px solid rgba(139, 58, 98, 0.1);
|
|
6
|
+
box-shadow: 0 10px 28px rgba(42, 16, 32, 0.05);
|
|
7
|
+
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.beauty-product-card:hover {
|
|
11
|
+
transform: translateY(-0.2rem);
|
|
12
|
+
border-color: rgba(139, 58, 98, 0.22);
|
|
13
|
+
box-shadow: 0 16px 36px rgba(42, 16, 32, 0.09);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.beauty-product-card__link {
|
|
17
|
+
min-width: 0;
|
|
18
|
+
text-decoration: none;
|
|
19
|
+
color: inherit;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.beauty-product-card__media {
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.beauty-product-card__image {
|
|
27
|
+
transition: transform 0.45s ease;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.beauty-product-card:hover .beauty-product-card__image {
|
|
31
|
+
transform: scale(1.04);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.beauty-product-card__placeholder {
|
|
35
|
+
background: linear-gradient(135deg, #faf7f5 0%, #f5ebe3 100%);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.beauty-product-card__badge {
|
|
39
|
+
position: absolute;
|
|
40
|
+
top: 0.625rem;
|
|
41
|
+
left: 0.625rem;
|
|
42
|
+
z-index: 12;
|
|
43
|
+
padding: 0.25rem 0.55rem;
|
|
44
|
+
font-size: 0.625rem;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
letter-spacing: 0.12em;
|
|
47
|
+
text-transform: uppercase;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.beauty-product-card__badge--sale {
|
|
51
|
+
background: #000000;
|
|
52
|
+
color: #ffffff;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.beauty-product-card .product-card__actions {
|
|
56
|
+
top: 0.5rem;
|
|
57
|
+
right: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.beauty-product-card .product-card__wishlist-btn,
|
|
61
|
+
.beauty-product-card .product-card__cart-btn {
|
|
62
|
+
width: 2.125rem;
|
|
63
|
+
height: 2.125rem;
|
|
64
|
+
border-radius: 999px;
|
|
65
|
+
border: 1px solid rgba(139, 58, 98, 0.14);
|
|
66
|
+
background: rgba(255, 255, 255, 0.95);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.beauty-product-card .product-card__wishlist-btn--active {
|
|
70
|
+
background: var(--color-brand-accent-muted);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.beauty-product-card .product-card__quick-add-wrap {
|
|
74
|
+
left: 0;
|
|
75
|
+
right: 0;
|
|
76
|
+
bottom: 0;
|
|
77
|
+
top: auto;
|
|
78
|
+
opacity: 1;
|
|
79
|
+
transform: none;
|
|
80
|
+
pointer-events: auto;
|
|
81
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.beauty-product-card .product-card__quick-add {
|
|
85
|
+
width: 100%;
|
|
86
|
+
border-radius: 0;
|
|
87
|
+
background: rgba(0, 0, 0, 0.82);
|
|
88
|
+
color: #ffffff;
|
|
89
|
+
letter-spacing: 0.14em;
|
|
90
|
+
text-transform: uppercase;
|
|
91
|
+
font-size: 0.6875rem;
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
min-height: 2.5rem;
|
|
94
|
+
backdrop-filter: blur(4px);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.beauty-product-card .product-card__quick-add:hover:not(:disabled) {
|
|
98
|
+
background: #000000;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.beauty-product-card:not(:hover) .product-card__quick-add-wrap {
|
|
102
|
+
opacity: 0;
|
|
103
|
+
transform: translateY(100%);
|
|
104
|
+
pointer-events: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.beauty-product-card:hover .product-card__quick-add-wrap {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
transform: translateY(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.beauty-product-card .product-card__cart-btn {
|
|
113
|
+
display: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.beauty-product-card .product-card__quick-add-label {
|
|
117
|
+
font-size: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.beauty-product-card .product-card__quick-add-label::after {
|
|
121
|
+
content: "Add to bag";
|
|
122
|
+
font-size: 0.6875rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.beauty-product-card__body {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
gap: 0.4rem;
|
|
129
|
+
padding: 0.875rem 0.875rem 1rem;
|
|
130
|
+
min-width: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.beauty-product-card__eyebrow {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-size: 0.625rem;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
letter-spacing: 0.14em;
|
|
138
|
+
text-transform: uppercase;
|
|
139
|
+
color: var(--color-brand-accent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.beauty-product-card__title {
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-family: var(--font-heading);
|
|
145
|
+
font-size: 0.9375rem;
|
|
146
|
+
font-weight: 500;
|
|
147
|
+
line-height: 1.35;
|
|
148
|
+
color: var(--color-text-heading);
|
|
149
|
+
display: -webkit-box;
|
|
150
|
+
-webkit-line-clamp: 2;
|
|
151
|
+
-webkit-box-orient: vertical;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
min-height: 2.5rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.beauty-product-card__footer {
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: flex-end;
|
|
159
|
+
justify-content: space-between;
|
|
160
|
+
gap: 0.75rem;
|
|
161
|
+
margin-top: 0.25rem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.beauty-product-card__rating {
|
|
165
|
+
margin: 0;
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 0.2rem;
|
|
169
|
+
font-size: 0.75rem;
|
|
170
|
+
font-weight: 600;
|
|
171
|
+
color: var(--color-text-heading);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.beauty-product-card__rating-stars {
|
|
175
|
+
color: #c9a86c;
|
|
176
|
+
font-size: 0.8125rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.beauty-product-card__prices {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
align-items: flex-end;
|
|
183
|
+
gap: 0.125rem;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.beauty-product-card__price {
|
|
187
|
+
font-size: 0.875rem;
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
color: var(--color-text-heading);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.beauty-product-card__compare {
|
|
193
|
+
font-size: 0.75rem;
|
|
194
|
+
color: var(--color-text-muted);
|
|
195
|
+
text-decoration: line-through;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.product-grid .beauty-product-card,
|
|
199
|
+
.product-rail__grid .beauty-product-card {
|
|
200
|
+
height: 100%;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.product-scroll__slide:has(.beauty-product-card) {
|
|
204
|
+
flex: 0 0 clamp(220px, 42vw, 260px);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@media (min-width: 768px) {
|
|
208
|
+
.product-scroll__slide:has(.beauty-product-card) {
|
|
209
|
+
flex: 0 0 260px;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@media (prefers-reduced-motion: reduce) {
|
|
214
|
+
.beauty-product-card,
|
|
215
|
+
.beauty-product-card__image,
|
|
216
|
+
.beauty-product-card .product-card__quick-add-wrap {
|
|
217
|
+
transition: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.beauty-product-card:not(:hover) .product-card__quick-add-wrap {
|
|
221
|
+
opacity: 1;
|
|
222
|
+
transform: none;
|
|
223
|
+
pointer-events: auto;
|
|
224
|
+
}
|
|
225
|
+
}
|
package/src/segment.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
3
|
+
import "./segment.css"
|
|
5
4
|
import type { HttpTypes } from "@medusajs/types"
|
|
6
|
-
import
|
|
5
|
+
import BeautyProductCard from "./beauty-product-card"
|
|
6
|
+
import DefaultProductCard from "./default-product-card"
|
|
7
|
+
|
|
8
|
+
export type ProductCardVariant = "default" | "beauty"
|
|
7
9
|
|
|
8
10
|
type Props = {
|
|
9
11
|
product: HttpTypes.StoreProduct
|
|
@@ -11,76 +13,17 @@ type Props = {
|
|
|
11
13
|
countryCode?: string
|
|
12
14
|
rating?: { rating?: number }
|
|
13
15
|
showWishlist?: boolean
|
|
16
|
+
variant?: ProductCardVariant
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
showWishlist = true,
|
|
22
|
-
}: Props) {
|
|
23
|
-
const variant = product.variants?.[0]
|
|
24
|
-
const price = variant?.calculated_price?.calculated_amount
|
|
25
|
-
const originalPrice = variant?.calculated_price?.original_amount
|
|
26
|
-
const currency = region?.currency_code || variant?.calculated_price?.currency_code || "inr"
|
|
27
|
-
const countryCode =
|
|
28
|
-
countryCodeProp?.toLowerCase() ??
|
|
29
|
-
region?.countries?.[0]?.iso_2?.toLowerCase() ??
|
|
30
|
-
process.env.NEXT_PUBLIC_DEFAULT_REGION?.toLowerCase() ??
|
|
31
|
-
"in"
|
|
32
|
-
|
|
33
|
-
const discountPct =
|
|
34
|
-
originalPrice && price && originalPrice > price
|
|
35
|
-
? Math.round(((originalPrice - price) / originalPrice) * 100)
|
|
36
|
-
: null
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<article
|
|
40
|
-
data-product-card-id={product.id}
|
|
41
|
-
className="product-card group flex flex-col w-full h-full relative"
|
|
42
|
-
>
|
|
43
|
-
<LocalizedLink href={`/products/${product.handle}`} className="product-card__link flex flex-col h-full">
|
|
44
|
-
<div className="product-card__media relative aspect-[3/4] bg-surface-muted overflow-hidden rounded-lg">
|
|
45
|
-
{product.thumbnail?.trim() ? (
|
|
46
|
-
<img
|
|
47
|
-
src={product.thumbnail.trim()}
|
|
48
|
-
alt={product.title || ""}
|
|
49
|
-
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
50
|
-
/>
|
|
51
|
-
) : (
|
|
52
|
-
<div className="w-full h-full bg-surface-muted" />
|
|
53
|
-
)}
|
|
54
|
-
|
|
55
|
-
{showWishlist && product.id ? (
|
|
56
|
-
<ProductCardActions product={product} countryCode={countryCode} />
|
|
57
|
-
) : null}
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<div className="product-card__info mt-3 flex flex-col gap-1 min-w-0">
|
|
61
|
-
<h3 className="product-card__title text-sm font-medium text-heading group-hover:text-brand-accent transition-colors line-clamp-2">
|
|
62
|
-
{product.title}
|
|
63
|
-
</h3>
|
|
64
|
-
|
|
65
|
-
{rating?.rating != null && rating.rating > 0 ? (
|
|
66
|
-
<p className="product-card__rating text-xs text-muted">★ {rating.rating.toFixed(1)}</p>
|
|
67
|
-
) : null}
|
|
19
|
+
function resolveProductCardVariant(variant?: ProductCardVariant): ProductCardVariant {
|
|
20
|
+
if (variant === "beauty" || variant === "default") return variant
|
|
21
|
+
const envVariant = process.env.NEXT_PUBLIC_PRODUCT_CARD_VARIANT?.toLowerCase()
|
|
22
|
+
return envVariant === "beauty" ? "beauty" : "default"
|
|
23
|
+
}
|
|
68
24
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<>
|
|
74
|
-
<span className="product-card__compare">{formatPrice(originalPrice, currency)}</span>
|
|
75
|
-
{discountPct != null ? (
|
|
76
|
-
<span className="product-card__discount">{discountPct}% OFF</span>
|
|
77
|
-
) : null}
|
|
78
|
-
</>
|
|
79
|
-
) : null}
|
|
80
|
-
</div>
|
|
81
|
-
) : null}
|
|
82
|
-
</div>
|
|
83
|
-
</LocalizedLink>
|
|
84
|
-
</article>
|
|
85
|
-
)
|
|
25
|
+
export default function ProductCard({ variant, ...props }: Props) {
|
|
26
|
+
const layout = resolveProductCardVariant(variant)
|
|
27
|
+
if (layout === "beauty") return <BeautyProductCard {...props} />
|
|
28
|
+
return <DefaultProductCard {...props} />
|
|
86
29
|
}
|