@pradip1995/segment-powerhouse-cart 0.1.1
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 +42 -0
- package/public/cart-sale.jpg +0 -0
- package/src/cart.css +413 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +10 -0
- package/src/segment.tsx +320 -0
- package/src/types.ts +37 -0
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pradip1995/segment-powerhouse-cart",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"./src/cart.css"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"public"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./src/index.ts",
|
|
17
|
+
"./manifest": "./src/manifest.ts"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@medusajs/types": "^2.0.0",
|
|
21
|
+
"@pradip1995/commerce-core": "^4.0.0",
|
|
22
|
+
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
23
|
+
"next": ">=15",
|
|
24
|
+
"react": ">=19",
|
|
25
|
+
"react-dom": ">=19"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@pradip1995/segment-analytics": "^0.2.4",
|
|
29
|
+
"@pradip1995/segment-primitives": "^0.4.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@medusajs/types": "^2.0.0",
|
|
33
|
+
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
34
|
+
"@types/react": "^19",
|
|
35
|
+
"react": "19.0.3",
|
|
36
|
+
"typescript": "^5.7.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"lint": "tsc --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
Binary file
|
package/src/cart.css
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
.powerhouse-cart {
|
|
2
|
+
--layout-container-max-width: 1400px;
|
|
3
|
+
color: #1d1d1d;
|
|
4
|
+
padding-bottom: 4rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.powerhouse-cart__inner {
|
|
8
|
+
max-width: var(--layout-container-max-width);
|
|
9
|
+
margin: 0 auto;
|
|
10
|
+
padding: 1.5rem 10px 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.powerhouse-cart__header {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
align-items: baseline;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: 0.75rem 1.5rem;
|
|
19
|
+
margin-bottom: 1.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.powerhouse-cart__header h1 {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-family: var(--font-heading, Georgia, serif);
|
|
25
|
+
font-size: 2.1rem;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
line-height: 1.15;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.powerhouse-cart__continue {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 0.45rem;
|
|
34
|
+
color: #1d1d1d;
|
|
35
|
+
font-family: var(--font-sans, inherit);
|
|
36
|
+
font-size: 0.9375rem;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.powerhouse-cart__continue:hover {
|
|
41
|
+
text-decoration: underline;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.powerhouse-cart__layout {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: 2rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.powerhouse-cart__main {
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.powerhouse-cart__shipping {
|
|
55
|
+
margin-bottom: 1.5rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.powerhouse-cart__shipping p {
|
|
59
|
+
margin: 0 0 0.65rem;
|
|
60
|
+
font-family: var(--font-sans, inherit);
|
|
61
|
+
font-size: 0.9375rem;
|
|
62
|
+
line-height: 1.5;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.powerhouse-cart__meter {
|
|
66
|
+
height: 4px;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
background: #e8e8e8;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.powerhouse-cart__progress {
|
|
72
|
+
height: 100%;
|
|
73
|
+
background: #1d1d1d;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.powerhouse-cart__empty {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
min-height: 12rem;
|
|
82
|
+
padding: 2rem 1rem 3rem;
|
|
83
|
+
text-align: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.powerhouse-cart__empty p {
|
|
87
|
+
margin: 0 0 1.25rem;
|
|
88
|
+
font-family: var(--font-sans, inherit);
|
|
89
|
+
font-size: 1.05rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.powerhouse-cart__empty-btn {
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 0.55rem;
|
|
96
|
+
min-height: 2.75rem;
|
|
97
|
+
padding: 0.65rem 1.15rem;
|
|
98
|
+
border-radius: 2px;
|
|
99
|
+
background: #1d1d1d;
|
|
100
|
+
color: #fff;
|
|
101
|
+
font-family: var(--font-sans, inherit);
|
|
102
|
+
font-size: 0.9375rem;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.powerhouse-cart__empty-btn:hover {
|
|
108
|
+
background: #000;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.powerhouse-cart__table {
|
|
112
|
+
width: 100%;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.powerhouse-cart__head {
|
|
116
|
+
display: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.powerhouse-cart__head span {
|
|
120
|
+
font-family: var(--font-sans, inherit);
|
|
121
|
+
font-size: 0.75rem;
|
|
122
|
+
font-weight: 600;
|
|
123
|
+
letter-spacing: 0.04em;
|
|
124
|
+
text-transform: uppercase;
|
|
125
|
+
color: #6b6b6b;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.powerhouse-cart__row {
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template-columns: 1fr;
|
|
131
|
+
gap: 0.85rem;
|
|
132
|
+
padding: 1.15rem 0;
|
|
133
|
+
border-top: 1px solid #e6e6e6;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.powerhouse-cart__row--pending {
|
|
137
|
+
opacity: 0.6;
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.powerhouse-cart__thumb {
|
|
142
|
+
position: relative;
|
|
143
|
+
display: block;
|
|
144
|
+
flex-shrink: 0;
|
|
145
|
+
width: 5.5rem;
|
|
146
|
+
height: 5.5rem;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
background: #f3f3f3;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.powerhouse-cart__thumb img {
|
|
152
|
+
object-fit: cover;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.powerhouse-cart__thumb-empty {
|
|
156
|
+
width: 100%;
|
|
157
|
+
height: 100%;
|
|
158
|
+
background: #ececec;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.powerhouse-cart__product {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: flex-start;
|
|
164
|
+
gap: 1rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.powerhouse-cart__copy {
|
|
168
|
+
min-width: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.powerhouse-cart__title {
|
|
172
|
+
display: block;
|
|
173
|
+
color: #1d1d1d;
|
|
174
|
+
font-family: var(--font-sans, inherit);
|
|
175
|
+
font-size: 0.9375rem;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
line-height: 1.35;
|
|
178
|
+
text-decoration: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.powerhouse-cart__title:hover {
|
|
182
|
+
text-decoration: underline;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.powerhouse-cart__variant,
|
|
186
|
+
.powerhouse-cart__mobile-price {
|
|
187
|
+
margin: 0.3rem 0 0;
|
|
188
|
+
font-family: var(--font-sans, inherit);
|
|
189
|
+
font-size: 0.8125rem;
|
|
190
|
+
color: #6b6b6b;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.powerhouse-cart__price,
|
|
194
|
+
.powerhouse-cart__total {
|
|
195
|
+
display: none;
|
|
196
|
+
font-family: var(--font-sans, inherit);
|
|
197
|
+
font-size: 0.9375rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.powerhouse-cart__actions {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 0.85rem;
|
|
204
|
+
margin-top: 0.75rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.powerhouse-cart__qty {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
border: 1px solid #d0d0d0;
|
|
211
|
+
border-radius: 2px;
|
|
212
|
+
background: #fff;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.powerhouse-cart__qty button,
|
|
216
|
+
.powerhouse-cart__qty span {
|
|
217
|
+
display: inline-flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
min-width: 2.1rem;
|
|
221
|
+
min-height: 2.1rem;
|
|
222
|
+
border: 0;
|
|
223
|
+
background: transparent;
|
|
224
|
+
color: #1d1d1d;
|
|
225
|
+
font-family: var(--font-sans, inherit);
|
|
226
|
+
font-size: 0.9375rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.powerhouse-cart__qty button {
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.powerhouse-cart__qty button:disabled {
|
|
234
|
+
opacity: 0.35;
|
|
235
|
+
cursor: default;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.powerhouse-cart__remove {
|
|
239
|
+
padding: 0;
|
|
240
|
+
border: 0;
|
|
241
|
+
background: none;
|
|
242
|
+
color: #6b6b6b;
|
|
243
|
+
font-family: var(--font-sans, inherit);
|
|
244
|
+
font-size: 0.8125rem;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.powerhouse-cart__remove:hover {
|
|
249
|
+
color: #1d1d1d;
|
|
250
|
+
text-decoration: underline;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.powerhouse-cart__sidebar {
|
|
254
|
+
min-width: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.powerhouse-cart__promo {
|
|
258
|
+
margin: 0 0 1rem;
|
|
259
|
+
font-family: var(--font-sans, inherit);
|
|
260
|
+
font-size: 0.9375rem;
|
|
261
|
+
line-height: 1.6;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.powerhouse-cart__promo a {
|
|
265
|
+
color: #1d1d1d;
|
|
266
|
+
font-weight: 700;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.powerhouse-cart__promo-image {
|
|
270
|
+
display: block;
|
|
271
|
+
width: 100%;
|
|
272
|
+
max-width: 18rem;
|
|
273
|
+
height: auto;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.powerhouse-cart__notes label {
|
|
277
|
+
display: block;
|
|
278
|
+
margin-bottom: 0.5rem;
|
|
279
|
+
font-family: var(--font-sans, inherit);
|
|
280
|
+
font-size: 0.9375rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.powerhouse-cart__notes textarea {
|
|
284
|
+
width: 100%;
|
|
285
|
+
min-height: 6.5rem;
|
|
286
|
+
margin-bottom: 1.25rem;
|
|
287
|
+
padding: 0.75rem 0.85rem;
|
|
288
|
+
border: 1px solid #d0d0d0;
|
|
289
|
+
border-radius: 2px;
|
|
290
|
+
background: #fff;
|
|
291
|
+
color: #1d1d1d;
|
|
292
|
+
font-family: var(--font-sans, inherit);
|
|
293
|
+
font-size: 0.9375rem;
|
|
294
|
+
resize: vertical;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.powerhouse-cart__notes textarea:focus {
|
|
298
|
+
outline: none;
|
|
299
|
+
border-color: #1d1d1d;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.powerhouse-cart__totals {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-direction: column;
|
|
305
|
+
gap: 0.55rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.powerhouse-cart__totals-row {
|
|
309
|
+
display: flex;
|
|
310
|
+
justify-content: space-between;
|
|
311
|
+
gap: 1rem;
|
|
312
|
+
font-family: var(--font-sans, inherit);
|
|
313
|
+
font-size: 0.9375rem;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.powerhouse-cart__totals-row--discount {
|
|
317
|
+
color: #1f6b3a;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.powerhouse-cart__tax-note {
|
|
321
|
+
margin: 0.15rem 0 0.85rem;
|
|
322
|
+
font-family: var(--font-sans, inherit);
|
|
323
|
+
font-size: 0.8125rem;
|
|
324
|
+
color: #6b6b6b;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.powerhouse-cart__checkout {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
justify-content: center;
|
|
331
|
+
min-height: 2.85rem;
|
|
332
|
+
padding: 0.7rem 1.15rem;
|
|
333
|
+
border-radius: 2px;
|
|
334
|
+
background: #1d1d1d;
|
|
335
|
+
color: #fff;
|
|
336
|
+
font-family: var(--font-sans, inherit);
|
|
337
|
+
font-size: 0.9375rem;
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
text-decoration: none;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.powerhouse-cart__checkout:hover {
|
|
343
|
+
background: #000;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@media screen and (min-width: 720px) {
|
|
347
|
+
.powerhouse-cart__inner {
|
|
348
|
+
padding-left: 20px;
|
|
349
|
+
padding-right: 20px;
|
|
350
|
+
padding-top: 2rem;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.powerhouse-cart__header h1 {
|
|
354
|
+
font-size: 2.4rem;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@media screen and (min-width: 960px) {
|
|
359
|
+
.powerhouse-cart__layout {
|
|
360
|
+
flex-direction: row;
|
|
361
|
+
align-items: flex-start;
|
|
362
|
+
gap: 3.5rem;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.powerhouse-cart__main {
|
|
366
|
+
flex: 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.powerhouse-cart__sidebar {
|
|
370
|
+
width: 18.5rem;
|
|
371
|
+
flex-shrink: 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.powerhouse-cart__head {
|
|
375
|
+
display: grid;
|
|
376
|
+
grid-template-columns: minmax(0, 1fr) 6.5rem 8.5rem 6.5rem;
|
|
377
|
+
gap: 1rem;
|
|
378
|
+
padding: 0 0 0.65rem;
|
|
379
|
+
border-bottom: 1px solid #e6e6e6;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.powerhouse-cart__head span:nth-child(2),
|
|
383
|
+
.powerhouse-cart__head span:nth-child(3),
|
|
384
|
+
.powerhouse-cart__head span:nth-child(4) {
|
|
385
|
+
text-align: center;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.powerhouse-cart__row {
|
|
389
|
+
grid-template-columns: minmax(0, 1fr) 6.5rem 8.5rem 6.5rem;
|
|
390
|
+
align-items: center;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.powerhouse-cart__mobile-price {
|
|
394
|
+
display: none;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.powerhouse-cart__price,
|
|
398
|
+
.powerhouse-cart__total {
|
|
399
|
+
display: block;
|
|
400
|
+
text-align: center;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.powerhouse-cart__actions {
|
|
404
|
+
margin-top: 0;
|
|
405
|
+
justify-content: center;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.powerhouse-cart__remove {
|
|
409
|
+
display: block;
|
|
410
|
+
width: 100%;
|
|
411
|
+
text-align: center;
|
|
412
|
+
}
|
|
413
|
+
}
|
package/src/index.ts
ADDED
package/src/manifest.ts
ADDED
package/src/segment.tsx
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import Image from "next/image"
|
|
4
|
+
import { useRouter } from "next/navigation"
|
|
5
|
+
import { useTransition } from "react"
|
|
6
|
+
import type { HttpTypes } from "@medusajs/types"
|
|
7
|
+
import { deleteLineItem, updateLineItem } from "@pradip1995/commerce-core/client/actions/cart"
|
|
8
|
+
import {
|
|
9
|
+
trackCartLineItemQuantityChange,
|
|
10
|
+
trackCartLineItemRemoved,
|
|
11
|
+
} from "@pradip1995/segment-analytics/cart-line-item-events"
|
|
12
|
+
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
13
|
+
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
14
|
+
import { resolveMediaUrl } from "@pradip1995/segment-primitives/resolve-media-url"
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_CHECKOUT_LABEL,
|
|
17
|
+
DEFAULT_CONTINUE_HREF,
|
|
18
|
+
DEFAULT_CONTINUE_LABEL,
|
|
19
|
+
DEFAULT_EMPTY_BUTTON,
|
|
20
|
+
DEFAULT_EMPTY_MESSAGE,
|
|
21
|
+
DEFAULT_FREE_SHIPPING_PREFIX,
|
|
22
|
+
DEFAULT_FREE_SHIPPING_THRESHOLD,
|
|
23
|
+
DEFAULT_NOTES_LABEL,
|
|
24
|
+
DEFAULT_PROMO_HREF,
|
|
25
|
+
DEFAULT_PROMO_IMAGE,
|
|
26
|
+
DEFAULT_PROMO_LINK_LABEL,
|
|
27
|
+
DEFAULT_PROMO_TEXT,
|
|
28
|
+
DEFAULT_TAX_NOTE,
|
|
29
|
+
DEFAULT_TITLE,
|
|
30
|
+
type PowerhouseCartProps,
|
|
31
|
+
} from "./types"
|
|
32
|
+
import "./cart.css"
|
|
33
|
+
|
|
34
|
+
function isPlaceholderVariant(title?: string | null) {
|
|
35
|
+
if (!title) return true
|
|
36
|
+
const normalized = title.toLowerCase()
|
|
37
|
+
return (
|
|
38
|
+
normalized === "default" ||
|
|
39
|
+
normalized.includes("default variant") ||
|
|
40
|
+
normalized.includes("default option")
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function lineTotal(item: HttpTypes.StoreCartLineItem) {
|
|
45
|
+
if (typeof item.total === "number") return item.total
|
|
46
|
+
return (item.unit_price ?? 0) * (item.quantity ?? 1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function lineItemImage(item: HttpTypes.StoreCartLineItem) {
|
|
50
|
+
const product = item.product || item.variant?.product || null
|
|
51
|
+
const firstVariantImage = item.variant?.images?.[0]
|
|
52
|
+
const candidates = [
|
|
53
|
+
item.thumbnail,
|
|
54
|
+
item.variant?.thumbnail,
|
|
55
|
+
product?.thumbnail,
|
|
56
|
+
typeof firstVariantImage === "string" ? firstVariantImage : firstVariantImage?.url,
|
|
57
|
+
product?.images?.[0]?.url,
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
for (const candidate of candidates) {
|
|
61
|
+
const resolved = resolveMediaUrl(candidate)
|
|
62
|
+
if (resolved) return resolved
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ContinueArrow() {
|
|
69
|
+
return (
|
|
70
|
+
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" aria-hidden>
|
|
71
|
+
<path
|
|
72
|
+
fillRule="evenodd"
|
|
73
|
+
clipRule="evenodd"
|
|
74
|
+
d="M14.7272 5.6364L10.3636 1.27279L11.6364 0L17.2728 5.6364L11.6364 11.2728L10.3636 10L14.7272 5.6364Z"
|
|
75
|
+
fill="currentColor"
|
|
76
|
+
/>
|
|
77
|
+
<path
|
|
78
|
+
fillRule="evenodd"
|
|
79
|
+
clipRule="evenodd"
|
|
80
|
+
d="M0 4.73645H16V6.53645H0V4.73645Z"
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
/>
|
|
83
|
+
</svg>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function CartIcon() {
|
|
88
|
+
return (
|
|
89
|
+
<svg width="22" height="21" viewBox="0 0 25 24" fill="currentColor" aria-hidden>
|
|
90
|
+
<path
|
|
91
|
+
fillRule="evenodd"
|
|
92
|
+
clipRule="evenodd"
|
|
93
|
+
d="M1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2H1.33877C1.61048 2.00005 2.00378 2.23945 2.10939 2.81599L5.01743 18.1859C5.12011 18.7286 5.64325 19.0852 6.18591 18.9826C6.25933 18.9658 6.31389 18.9692 6.34159 18.9692H18.8179C19.0302 18.9691 19.2141 18.9765 19.4075 18.9842C20.0255 19.0043 20.3767 19.0061 20.7177 18.9406C21.08 18.871 21.4685 18.7189 21.8028 18.3961C22.1291 18.081 22.3266 17.6772 22.4479 17.2384L23.944 8.46865C24.2528 7.20993 23.2684 5.99987 21.9896 6H4.74727L4.07666 2.45562C3.83133 1.12381 2.76159 0 1.33889 0H1ZM5.12568 8L6.8227 16.9692H18.8179C19.0686 16.9691 19.3257 16.9793 19.5406 16.9877C20.0684 17.0047 20.2307 16.9976 20.3403 16.9766C20.4059 16.96 20.4639 16.8918 20.5077 16.7487L21.9794 8.09186C21.9908 8.00108 21.989 8H5.12568ZM9.36658 24C10.195 24 10.8666 23.2982 10.8666 22.4326C10.8666 21.567 10.195 20.8653 9.36658 20.8653C8.53815 20.8653 7.86658 21.567 7.86658 22.4326C7.86658 23.2982 8.53815 24 9.36658 24ZM19.5048 22.4326C19.5048 23.2982 18.8332 24 18.0048 24C17.1763 24 16.5048 23.2982 16.5048 22.4326C16.5048 21.567 17.1763 20.8653 18.0048 20.8653C18.8332 20.8653 19.5048 21.567 19.5048 22.4326Z"
|
|
94
|
+
/>
|
|
95
|
+
</svg>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ShippingBar({
|
|
100
|
+
subtotal,
|
|
101
|
+
threshold,
|
|
102
|
+
currency,
|
|
103
|
+
prefix,
|
|
104
|
+
}: {
|
|
105
|
+
subtotal: number
|
|
106
|
+
threshold: number
|
|
107
|
+
currency: string
|
|
108
|
+
prefix: string
|
|
109
|
+
}) {
|
|
110
|
+
const progress = threshold > 0 ? Math.min(100, (subtotal / threshold) * 100) : 0
|
|
111
|
+
return (
|
|
112
|
+
<div className="powerhouse-cart__shipping">
|
|
113
|
+
<p>
|
|
114
|
+
{prefix} <strong>{formatPrice(threshold, currency)}</strong>!
|
|
115
|
+
</p>
|
|
116
|
+
<div className="powerhouse-cart__meter" aria-hidden>
|
|
117
|
+
<div className="powerhouse-cart__progress" style={{ width: `${progress}%` }} />
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function LineItemRow({
|
|
124
|
+
item,
|
|
125
|
+
currency,
|
|
126
|
+
countryCode,
|
|
127
|
+
}: {
|
|
128
|
+
item: HttpTypes.StoreCartLineItem
|
|
129
|
+
currency: string
|
|
130
|
+
countryCode: string
|
|
131
|
+
}) {
|
|
132
|
+
const router = useRouter()
|
|
133
|
+
const [pending, startTransition] = useTransition()
|
|
134
|
+
const thumbnail = lineItemImage(item)
|
|
135
|
+
const variantLabel = item.variant?.title
|
|
136
|
+
const showVariant = Boolean(variantLabel && !isPlaceholderVariant(variantLabel))
|
|
137
|
+
|
|
138
|
+
function changeQty(next: number) {
|
|
139
|
+
if (next < 1 || !item.id) return
|
|
140
|
+
const previousQuantity = item.quantity ?? 1
|
|
141
|
+
startTransition(async () => {
|
|
142
|
+
await updateLineItem({ lineId: item.id, quantity: next })
|
|
143
|
+
trackCartLineItemQuantityChange(item, currency, previousQuantity, next)
|
|
144
|
+
router.refresh()
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function remove() {
|
|
149
|
+
if (!item.id) return
|
|
150
|
+
startTransition(async () => {
|
|
151
|
+
trackCartLineItemRemoved(item, currency)
|
|
152
|
+
await deleteLineItem(item.id)
|
|
153
|
+
router.refresh()
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<article className={`powerhouse-cart__row${pending ? " powerhouse-cart__row--pending" : ""}`}>
|
|
159
|
+
<div className="powerhouse-cart__product">
|
|
160
|
+
<LocalizedLink
|
|
161
|
+
href={`/products/${item.product_handle}`}
|
|
162
|
+
countryCode={countryCode}
|
|
163
|
+
className="powerhouse-cart__thumb"
|
|
164
|
+
>
|
|
165
|
+
{thumbnail ? (
|
|
166
|
+
<Image src={thumbnail} alt={item.title || "Product"} fill sizes="88px" unoptimized />
|
|
167
|
+
) : (
|
|
168
|
+
<div className="powerhouse-cart__thumb-empty" />
|
|
169
|
+
)}
|
|
170
|
+
</LocalizedLink>
|
|
171
|
+
<div className="powerhouse-cart__copy">
|
|
172
|
+
<LocalizedLink
|
|
173
|
+
href={`/products/${item.product_handle}`}
|
|
174
|
+
countryCode={countryCode}
|
|
175
|
+
className="powerhouse-cart__title"
|
|
176
|
+
>
|
|
177
|
+
{item.title}
|
|
178
|
+
</LocalizedLink>
|
|
179
|
+
{showVariant ? <p className="powerhouse-cart__variant">{variantLabel}</p> : null}
|
|
180
|
+
<p className="powerhouse-cart__mobile-price">{formatPrice(item.unit_price, currency)}</p>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
<p className="powerhouse-cart__price">{formatPrice(item.unit_price, currency)}</p>
|
|
184
|
+
<div className="powerhouse-cart__actions">
|
|
185
|
+
<div className="powerhouse-cart__qty">
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={() => changeQty((item.quantity || 1) - 1)}
|
|
189
|
+
disabled={pending || (item.quantity ?? 1) <= 1}
|
|
190
|
+
aria-label="Decrease quantity"
|
|
191
|
+
>
|
|
192
|
+
−
|
|
193
|
+
</button>
|
|
194
|
+
<span>{item.quantity}</span>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
onClick={() => changeQty((item.quantity || 0) + 1)}
|
|
198
|
+
disabled={pending}
|
|
199
|
+
aria-label="Increase quantity"
|
|
200
|
+
>
|
|
201
|
+
+
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
<button type="button" className="powerhouse-cart__remove" onClick={remove} disabled={pending}>
|
|
205
|
+
Remove
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
<p className="powerhouse-cart__total">{formatPrice(lineTotal(item), currency)}</p>
|
|
209
|
+
</article>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default function PowerhouseCart(props: PowerhouseCartProps) {
|
|
214
|
+
const countryCode = props.countryCode?.toLowerCase() || "in"
|
|
215
|
+
const cart = props.cart
|
|
216
|
+
const items = cart?.items || []
|
|
217
|
+
const currency = cart?.currency_code || "inr"
|
|
218
|
+
const subtotal = cart?.subtotal ?? 0
|
|
219
|
+
const title = props.title || DEFAULT_TITLE
|
|
220
|
+
const continueLabel = props.continueLabel || DEFAULT_CONTINUE_LABEL
|
|
221
|
+
const continueHref = props.continueHref || DEFAULT_CONTINUE_HREF
|
|
222
|
+
const emptyMessage = props.emptyMessage || DEFAULT_EMPTY_MESSAGE
|
|
223
|
+
const emptyButton = props.emptyButton || DEFAULT_EMPTY_BUTTON
|
|
224
|
+
const checkoutLabel = props.checkoutLabel || DEFAULT_CHECKOUT_LABEL
|
|
225
|
+
const notesLabel = props.notesLabel || DEFAULT_NOTES_LABEL
|
|
226
|
+
const taxNote = props.taxNote || DEFAULT_TAX_NOTE
|
|
227
|
+
const threshold = props.freeShippingThreshold ?? DEFAULT_FREE_SHIPPING_THRESHOLD
|
|
228
|
+
const shippingPrefix = props.freeShippingPrefix || DEFAULT_FREE_SHIPPING_PREFIX
|
|
229
|
+
const promoText = props.promoText ?? DEFAULT_PROMO_TEXT
|
|
230
|
+
const promoLinkLabel = props.promoLinkLabel || DEFAULT_PROMO_LINK_LABEL
|
|
231
|
+
const promoHref = props.promoHref || DEFAULT_PROMO_HREF
|
|
232
|
+
const promoImage = props.promoImage ?? DEFAULT_PROMO_IMAGE
|
|
233
|
+
const isEmpty = items.length === 0
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<section className="powerhouse-cart">
|
|
237
|
+
<div className="powerhouse-cart__inner">
|
|
238
|
+
<header className="powerhouse-cart__header">
|
|
239
|
+
<h1>{title}</h1>
|
|
240
|
+
<LocalizedLink href={continueHref} countryCode={countryCode} className="powerhouse-cart__continue">
|
|
241
|
+
{continueLabel}
|
|
242
|
+
<ContinueArrow />
|
|
243
|
+
</LocalizedLink>
|
|
244
|
+
</header>
|
|
245
|
+
|
|
246
|
+
<div className="powerhouse-cart__layout">
|
|
247
|
+
<div className="powerhouse-cart__main">
|
|
248
|
+
<ShippingBar
|
|
249
|
+
subtotal={subtotal}
|
|
250
|
+
threshold={threshold}
|
|
251
|
+
currency={currency}
|
|
252
|
+
prefix={shippingPrefix}
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
{isEmpty ? (
|
|
256
|
+
<div className="powerhouse-cart__empty">
|
|
257
|
+
<p>{emptyMessage}</p>
|
|
258
|
+
<LocalizedLink href={continueHref} countryCode={countryCode} className="powerhouse-cart__empty-btn">
|
|
259
|
+
<CartIcon />
|
|
260
|
+
{emptyButton}
|
|
261
|
+
</LocalizedLink>
|
|
262
|
+
</div>
|
|
263
|
+
) : (
|
|
264
|
+
<div className="powerhouse-cart__table">
|
|
265
|
+
<div className="powerhouse-cart__head">
|
|
266
|
+
<span>Product</span>
|
|
267
|
+
<span>Price</span>
|
|
268
|
+
<span>Quantity</span>
|
|
269
|
+
<span>Total</span>
|
|
270
|
+
</div>
|
|
271
|
+
{items.map((item) => (
|
|
272
|
+
<LineItemRow key={item.id} item={item} currency={currency} countryCode={countryCode} />
|
|
273
|
+
))}
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<aside className="powerhouse-cart__sidebar">
|
|
279
|
+
{isEmpty ? (
|
|
280
|
+
<>
|
|
281
|
+
{promoText ? (
|
|
282
|
+
<p className="powerhouse-cart__promo">
|
|
283
|
+
{promoText}{" "}
|
|
284
|
+
<LocalizedLink href={promoHref} countryCode={countryCode}>
|
|
285
|
+
<strong>{promoLinkLabel}</strong>
|
|
286
|
+
</LocalizedLink>
|
|
287
|
+
</p>
|
|
288
|
+
) : null}
|
|
289
|
+
{promoImage ? <img src={promoImage} alt="" className="powerhouse-cart__promo-image" /> : null}
|
|
290
|
+
</>
|
|
291
|
+
) : (
|
|
292
|
+
<>
|
|
293
|
+
<div className="powerhouse-cart__notes">
|
|
294
|
+
<label htmlFor="powerhouse-cart-note">{notesLabel}</label>
|
|
295
|
+
<textarea id="powerhouse-cart-note" name="note" rows={4} />
|
|
296
|
+
</div>
|
|
297
|
+
<div className="powerhouse-cart__totals">
|
|
298
|
+
<div className="powerhouse-cart__totals-row">
|
|
299
|
+
<span>Subtotal</span>
|
|
300
|
+
<strong>{formatPrice(cart?.subtotal, currency)}</strong>
|
|
301
|
+
</div>
|
|
302
|
+
{(cart?.discount_total ?? 0) > 0 ? (
|
|
303
|
+
<div className="powerhouse-cart__totals-row powerhouse-cart__totals-row--discount">
|
|
304
|
+
<span>Discount</span>
|
|
305
|
+
<strong>-{formatPrice(cart?.discount_total, currency)}</strong>
|
|
306
|
+
</div>
|
|
307
|
+
) : null}
|
|
308
|
+
</div>
|
|
309
|
+
<p className="powerhouse-cart__tax-note">{taxNote}</p>
|
|
310
|
+
<LocalizedLink href="/checkout" countryCode={countryCode} className="powerhouse-cart__checkout">
|
|
311
|
+
{checkoutLabel}
|
|
312
|
+
</LocalizedLink>
|
|
313
|
+
</>
|
|
314
|
+
)}
|
|
315
|
+
</aside>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</section>
|
|
319
|
+
)
|
|
320
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HttpTypes } from "@medusajs/types"
|
|
2
|
+
|
|
3
|
+
export type PowerhouseCartProps = {
|
|
4
|
+
cart?: HttpTypes.StoreCart | null
|
|
5
|
+
countryCode?: string
|
|
6
|
+
title?: string
|
|
7
|
+
continueLabel?: string
|
|
8
|
+
continueHref?: string
|
|
9
|
+
emptyMessage?: string
|
|
10
|
+
emptyButton?: string
|
|
11
|
+
checkoutLabel?: string
|
|
12
|
+
notesLabel?: string
|
|
13
|
+
notesPlaceholder?: string
|
|
14
|
+
taxNote?: string
|
|
15
|
+
freeShippingThreshold?: number
|
|
16
|
+
freeShippingPrefix?: string
|
|
17
|
+
promoText?: string
|
|
18
|
+
promoLinkLabel?: string
|
|
19
|
+
promoHref?: string
|
|
20
|
+
promoImage?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_TITLE = "Your cart"
|
|
24
|
+
export const DEFAULT_CONTINUE_LABEL = "Continue shopping"
|
|
25
|
+
export const DEFAULT_CONTINUE_HREF = "/store"
|
|
26
|
+
export const DEFAULT_EMPTY_MESSAGE = "Your cart is empty"
|
|
27
|
+
export const DEFAULT_EMPTY_BUTTON = "Continue shopping"
|
|
28
|
+
export const DEFAULT_CHECKOUT_LABEL = "Checkout"
|
|
29
|
+
export const DEFAULT_NOTES_LABEL = "Add a note to your order"
|
|
30
|
+
export const DEFAULT_NOTES_PLACEHOLDER = ""
|
|
31
|
+
export const DEFAULT_TAX_NOTE = "Taxes and shipping calculated at checkout"
|
|
32
|
+
export const DEFAULT_FREE_SHIPPING_THRESHOLD = 150
|
|
33
|
+
export const DEFAULT_FREE_SHIPPING_PREFIX = "Free shipping on orders over"
|
|
34
|
+
export const DEFAULT_PROMO_TEXT = "Save up to 50% on our clearance items."
|
|
35
|
+
export const DEFAULT_PROMO_LINK_LABEL = "Shop clearance sale →"
|
|
36
|
+
export const DEFAULT_PROMO_HREF = "/store"
|
|
37
|
+
export const DEFAULT_PROMO_IMAGE = "/cart-sale.jpg"
|