@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 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
@@ -0,0 +1,2 @@
1
+ export { default } from "./segment"
2
+ export { default as manifest } from "./manifest"
@@ -0,0 +1,10 @@
1
+ import type { SegmentManifest } from "@pradip1995/plugin-sdk"
2
+
3
+ const manifest: SegmentManifest = {
4
+ id: "powerhouse-cart",
5
+ type: "segment",
6
+ version: "0.1.0",
7
+ compatibleFramework: ["^1.0.0"],
8
+ dataKey: "cartPage",
9
+ }
10
+ export default manifest
@@ -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"