@thorprovider/blocks 0.1.3 → 1.0.0

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +73 -29
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  7. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  8. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  9. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  10. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  11. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  12. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  13. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  14. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  15. package/src/blocks/e-commerce/product-list/product-card.tsx +424 -0
  16. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  17. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  18. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  19. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  20. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  21. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  22. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  23. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  24. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  25. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  26. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  27. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  28. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  29. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  30. package/src/blocks/marketing/features/feature.tsx +195 -0
  31. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +27 -9
  32. package/src/blocks/marketing/navbars/navbar-4.test.tsx +33 -2
  33. package/src/blocks/marketing/navbars/navbar-4.tsx +29 -11
  34. package/src/index.ts +77 -48
  35. package/src/lib/contracts-curated.test.ts +42 -24
  36. package/src/lib/contracts.test.ts +143 -2
  37. package/src/lib/contracts.ts +346 -14
  38. package/src/ui/button-group.tsx +1 -1
  39. package/src/ui/carousel.tsx +1 -1
  40. package/src/ui/dialog.tsx +1 -1
  41. package/src/ui/field.tsx +2 -2
  42. package/src/ui/input-group.tsx +3 -3
  43. package/src/ui/pagination.tsx +1 -1
  44. package/src/ui/sidebar.tsx +7 -7
  45. package/src/ui/toggle-group.tsx +1 -1
  46. package/tsconfig.build.json +12 -0
  47. package/tsup.config.ts +11 -7
  48. package/.turbo/turbo-build.log +0 -24
  49. package/.turbo/turbo-test.log +0 -47
  50. package/.turbo/turbo-type-check.log +0 -0
  51. package/dist/.tsbuildinfo +0 -1
  52. package/dist/contracts.d.mts +0 -1732
  53. package/dist/contracts.d.ts +0 -1732
  54. package/dist/contracts.js +0 -423
  55. package/dist/contracts.js.map +0 -1
  56. package/dist/contracts.mjs +0 -365
  57. package/dist/contracts.mjs.map +0 -1
  58. package/dist/index.d.mts +0 -3068
  59. package/dist/index.d.ts +0 -3068
  60. package/dist/index.js +0 -17834
  61. package/dist/index.js.map +0 -1
  62. package/dist/index.mjs +0 -17740
  63. package/dist/index.mjs.map +0 -1
  64. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  65. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  66. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  67. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  68. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  69. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  70. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  71. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  72. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  73. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  74. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  75. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  76. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  77. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  78. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  79. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  80. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  81. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  82. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  83. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  84. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  85. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  86. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  87. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  88. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  89. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  90. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  91. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  92. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  93. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  94. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  95. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  96. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  97. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  98. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  99. package/src/blocks/marketing/features/feature-2.tsx +0 -82
@@ -1,74 +1,48 @@
1
1
  'use client'
2
2
 
3
- import { useState } from 'react'
4
- import { ShoppingCart, Zap, Clock, CreditCard, Wallet, Minus, Plus, X } from 'lucide-react'
3
+ import { useId, useState } from 'react'
4
+ import { ShoppingCart as ShoppingCartIcon, Zap, Clock, CreditCard, Wallet, Minus, Plus, X } from 'lucide-react'
5
5
  import type { LucideIcon } from 'lucide-react'
6
6
  import type { Money } from '@thorprovider/types'
7
7
  import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card'
8
8
  import { Button } from '../../../ui/button'
9
+ import { Input } from '../../../ui/input'
9
10
  import { Separator } from '../../../ui/separator'
10
11
  import { Progress } from '../../../ui/progress'
11
12
  import { cn } from '../../../lib/utils'
12
13
  import { formatMoney, isAvailableMoney } from '../../../lib/money'
14
+ import type { CartTotals, ShoppingCartItem, ShoppingCartSuggestion } from '../../../lib/contracts'
13
15
 
14
- export interface ShoppingCart4Item {
15
- /** Backend cart line-item id — the stable identity for update/removal. */
16
- id: string
17
- name: string
18
- price: number
19
- quantity: number
20
- /** Optional; an absent image is omitted rather than serialized as `""`. */
21
- image?: string
22
- variant?: string
23
- /** Canonical backend per-unit price; preferred for display when present. */
24
- unitPrice?: Money
25
- /** Canonical backend line total; preferred over `price * quantity`. */
26
- lineTotal?: Money
27
- }
28
-
29
- export interface ShoppingCart4Totals {
30
- subtotal: Money
31
- discount?: Money
32
- tax?: Money
33
- shipping?: Money
34
- total: Money
35
- }
36
-
37
- export type ShoppingCart4TotalsState = 'idle' | 'pending' | 'error'
38
-
39
- export interface ShoppingCart4Promotion {
16
+ /** Host-supplied promotion definition. Icons are React-side only. */
17
+ export interface ShoppingCartPromotion {
40
18
  threshold: number
41
19
  reward: string
42
20
  icon: LucideIcon
43
21
  }
44
22
 
45
- export interface ShoppingCart4RecentlyViewed {
46
- id: string
47
- name: string
48
- price: number
49
- image?: string
50
- }
23
+ export type ShoppingCartTotalsState = 'idle' | 'pending' | 'error'
51
24
 
52
- export type ShoppingCart4Presentation = 'compact' | 'page'
25
+ export type ShoppingCartPresentation = 'compact' | 'page'
53
26
 
54
- export interface ShoppingCart4Props {
55
- items: ShoppingCart4Item[]
56
- promotions: ShoppingCart4Promotion[]
57
- recentlyViewed?: ShoppingCart4RecentlyViewed[]
27
+ export interface ShoppingCartProps {
28
+ /** Canonical cart lines keyed by backend cart line-item id. */
29
+ items: ShoppingCartItem[]
30
+ promotions?: ShoppingCartPromotion[]
31
+ recentlyViewed?: ShoppingCartSuggestion[]
58
32
  /**
59
33
  * Layout presentation. `compact` (default) is the collapsible quick-cart
60
34
  * card; `page` is a wide, always-expanded review layout with a primary
61
35
  * heading, no collapse control, and normal page scrolling. Both use the same
62
36
  * line controls and authoritative monetary rendering.
63
37
  */
64
- presentation?: ShoppingCart4Presentation
38
+ presentation?: ShoppingCartPresentation
65
39
  /**
66
- * Backend-authoritative totals. The curated cart renders these verbatim; it
67
- * never computes subtotal/tax/shipping/total locally.
40
+ * Backend-authoritative totals. The cart renders these verbatim; it never
41
+ * computes subtotal/tax/shipping/total locally.
68
42
  */
69
- totals?: ShoppingCart4Totals
43
+ totals?: CartTotals
70
44
  /** Controlled state of the totals fetch/mutation. */
71
- totalsState?: ShoppingCart4TotalsState
45
+ totalsState?: ShoppingCartTotalsState
72
46
  totalsErrorLabel?: string
73
47
  /** Shown when a required total amount is absent or malformed (never as zero). */
74
48
  totalsUnavailableLabel?: string
@@ -78,10 +52,6 @@ export interface ShoppingCart4Props {
78
52
  onRetryTotals?: () => void
79
53
  /** Override money formatting (locale, currency). Defaults to `Money.currencyCode`. */
80
54
  formatMoney?: (money: Money) => string
81
- /** @deprecated legacy numeric formatter, used only when `totals` is absent. */
82
- formatPrice?: (value: number) => string
83
- /** @deprecated legacy local tax rate, used only when `totals` is absent. */
84
- taxRate?: number
85
55
  // labels (Spanish defaults):
86
56
  titleLabel?: string // 'Carrito de compras'
87
57
  subtotalLabel?: string // 'Subtotal'
@@ -100,6 +70,9 @@ export interface ShoppingCart4Props {
100
70
  estimatedDeliveryLabel?: string
101
71
  secureCheckoutLabel?: string // 'Pago seguro • SSL cifrado'
102
72
  recentlyViewedLabel?: string // 'Vistos recientemente'
73
+ promoPromptLabel?: string // '¿Tienes un código de descuento?'
74
+ promoPlaceholderLabel?: string // 'Introduce tu código'
75
+ promoApplyLabel?: string // 'Aplicar'
103
76
  collapseLabel?: string // 'Contraer carrito'
104
77
  expandLabel?: string // 'Expandir carrito'
105
78
  checkoutLabel?: string // 'Ver carrito y finalizar'
@@ -127,12 +100,19 @@ export interface ShoppingCart4Props {
127
100
  onQuickCheckout?: () => void
128
101
  onPayWithApple?: () => void
129
102
  onPayWithGoogle?: () => void
103
+ /**
104
+ * Report an entered promotion code to the host. The block never validates
105
+ * or resolves codes; the authoritative result arrives through `totals`
106
+ * (discount) and `promotions`. When absent, the promo affordance is not
107
+ * rendered.
108
+ */
109
+ onApplyPromo?: (code: string) => void
130
110
  onRecentlyViewedClick?: (id: string) => void
131
111
  /** Page presentation only: navigate back to browsing. */
132
112
  onContinueShopping?: () => void
133
113
  }
134
114
 
135
- const usdFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
115
+ const fallbackFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
136
116
 
137
117
  const interpolate = (template: string, values: Record<string, string>) =>
138
118
  template.replace(/\$\{(\w+)\}|\{(\w+)\}/g, (match, dollarKey, braceKey) => {
@@ -140,7 +120,7 @@ const interpolate = (template: string, values: Record<string, string>) =>
140
120
  return values[key] ?? match
141
121
  })
142
122
 
143
- export function ShoppingCart4({
123
+ export function ShoppingCart({
144
124
  items,
145
125
  promotions,
146
126
  recentlyViewed,
@@ -153,7 +133,6 @@ export function ShoppingCart4({
153
133
  retryLabel = 'Reintentar',
154
134
  onRetryTotals,
155
135
  formatMoney: formatMoneyProp,
156
- formatPrice,
157
136
  titleLabel = 'Carrito de compras',
158
137
  subtotalLabel = 'Subtotal',
159
138
  discountLabel = 'Descuento',
@@ -171,6 +150,9 @@ export function ShoppingCart4({
171
150
  estimatedDeliveryLabel,
172
151
  secureCheckoutLabel = 'Pago seguro • SSL cifrado',
173
152
  recentlyViewedLabel = 'Vistos recientemente',
153
+ promoPromptLabel = '¿Tienes un código de descuento?',
154
+ promoPlaceholderLabel = 'Introduce tu código',
155
+ promoApplyLabel = 'Aplicar',
174
156
  collapseLabel = 'Contraer carrito',
175
157
  expandLabel = 'Expandir carrito',
176
158
  checkoutLabel = 'Ver carrito y finalizar',
@@ -189,16 +171,29 @@ export function ShoppingCart4({
189
171
  onQuickCheckout,
190
172
  onPayWithApple,
191
173
  onPayWithGoogle,
174
+ onApplyPromo,
192
175
  onRecentlyViewedClick,
193
176
  onContinueShopping,
194
- }: ShoppingCart4Props) {
177
+ }: ShoppingCartProps) {
195
178
  const [isExpanded, setIsExpanded] = useState(true)
196
179
  const [quickCheckout, setQuickCheckout] = useState(false)
180
+ const [promoCode, setPromoCode] = useState('')
181
+ const promoInputId = useId()
197
182
 
198
183
  const isPage = presentation === 'page'
199
184
  // The page presentation is always expanded; only the compact card collapses.
200
185
  const expanded = isPage ? true : isExpanded
201
- const format = formatPrice ?? (value => usdFormatter.format(value))
186
+ // Display currency derives from the authoritative Money subtotal; the USD
187
+ // formatter is only the fallback when no authoritative currency exists.
188
+ const format = formatMoneyProp
189
+ ? (value: number) => {
190
+ const money: Money = { amount: String(value), currencyCode: totals?.subtotal?.currencyCode ?? 'USD' }
191
+ return formatMoneyProp(money)
192
+ }
193
+ : totals?.subtotal?.currencyCode && totals.subtotal.currencyCode !== 'USD'
194
+ ? (value: number) =>
195
+ new Intl.NumberFormat('en-US', { style: 'currency', currency: totals.subtotal.currencyCode }).format(value)
196
+ : (value: number) => fallbackFormatter.format(value)
202
197
  const money = (value: Money | null | undefined) => formatMoney(value, formatMoneyProp)
203
198
  const subtotalAmount = isAvailableMoney(totals?.subtotal) ? Number(totals.subtotal.amount) : 0
204
199
  const itemCount = items.reduce((sum, item) => sum + item.quantity, 0)
@@ -207,7 +202,7 @@ export function ShoppingCart4({
207
202
  // conflicting edits and checkout until the backend responds.
208
203
  const mutationsDisabled = totalsState === 'pending'
209
204
 
210
- const sortedPromotions = [...promotions].sort((a, b) => a.threshold - b.threshold)
205
+ const sortedPromotions = [...(promotions ?? [])].sort((a, b) => a.threshold - b.threshold)
211
206
  const currentPromo = (() => {
212
207
  if (!isAvailableMoney(totals?.subtotal)) return null
213
208
  for (let i = sortedPromotions.length - 1; i >= 0; i--) {
@@ -223,11 +218,12 @@ export function ShoppingCart4({
223
218
  ? Math.min(100, (subtotalAmount / currentPromo.threshold) * 100)
224
219
  : 100
225
220
 
226
- const lineAmount = (item: ShoppingCart4Item): string | null => {
227
- // A supplied line total that is absent/malformed must not fall back to a
228
- // numeric guess; only an entirely absent line total uses the legacy field.
229
- if (item.lineTotal !== undefined) return money(item.lineTotal)
230
- return format(item.price * item.quantity)
221
+ const lineAmount = (item: ShoppingCartItem): string | null => {
222
+ // The backend-authoritative line total renders verbatim. A malformed amount
223
+ // is unavailable (never a fabricated zero); `unitPrice` is never multiplied
224
+ // locally — the cart contract forbids local `unit × qty` arithmetic.
225
+ if (item.lineTotal === undefined) return null
226
+ return money(item.lineTotal)
231
227
  }
232
228
 
233
229
  const renderTotals = () => {
@@ -305,6 +301,36 @@ export function ShoppingCart4({
305
301
  )
306
302
  }
307
303
 
304
+ const renderPromoEntry = () =>
305
+ onApplyPromo ? (
306
+ <form
307
+ className='flex flex-col gap-2'
308
+ onSubmit={event => {
309
+ event.preventDefault()
310
+ const code = promoCode.trim()
311
+ if (!code) return
312
+ onApplyPromo(code)
313
+ setPromoCode('')
314
+ }}
315
+ >
316
+ <label className='text-sm' htmlFor={promoInputId}>
317
+ {promoPromptLabel}
318
+ </label>
319
+ <div className='flex gap-2'>
320
+ <Input
321
+ id={promoInputId}
322
+ value={promoCode}
323
+ onChange={event => setPromoCode(event.target.value)}
324
+ placeholder={promoPlaceholderLabel}
325
+ className='h-9 flex-1'
326
+ />
327
+ <Button type='submit' variant='outline' size='sm' className='h-9' disabled={mutationsDisabled}>
328
+ {promoApplyLabel}
329
+ </Button>
330
+ </div>
331
+ </form>
332
+ ) : null
333
+
308
334
  return (
309
335
  <section className='py-8'>
310
336
  <div className={cn('mx-auto px-4', isPage ? 'max-w-3xl' : 'max-w-md')}>
@@ -315,7 +341,7 @@ export function ShoppingCart4({
315
341
  {/* Header */}
316
342
  <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-3'>
317
343
  <CardTitle className='flex items-center gap-2'>
318
- <ShoppingCart className='size-5' />
344
+ <ShoppingCartIcon className='size-5' />
319
345
  {titleLabel} ({itemCount})
320
346
  </CardTitle>
321
347
  {!isPage ? (
@@ -335,7 +361,7 @@ export function ShoppingCart4({
335
361
  <CardContent className='flex flex-col gap-6'>
336
362
  {items.length === 0 ? (
337
363
  <div className='flex flex-col items-center justify-center gap-4 py-6 text-center'>
338
- <ShoppingCart className='text-muted-foreground/50 size-10' />
364
+ <ShoppingCartIcon className='text-muted-foreground/50 size-10' />
339
365
  <p className='text-muted-foreground text-sm'>{emptyLabel}</p>
340
366
  {isPage && onContinueShopping ? (
341
367
  <Button variant='outline' onClick={onContinueShopping}>
@@ -452,6 +478,9 @@ export function ShoppingCart4({
452
478
  {/* Order Summary — backend-authoritative only */}
453
479
  <div className='flex flex-col gap-2'>{renderTotals()}</div>
454
480
 
481
+ {/* Promo code entry — reports to the host, never resolves locally */}
482
+ {renderPromoEntry()}
483
+
455
484
  {/* Quick Actions */}
456
485
  <div className='flex flex-col gap-3'>
457
486
  {/* Express Checkout Toggle — only when the host supplies a handler */}
@@ -551,34 +580,39 @@ export function ShoppingCart4({
551
580
  </div>
552
581
  )}
553
582
 
554
- {/* Recently Viewed */}
583
+ {/* Recently Viewed — host-supplied suggestions only */}
555
584
  {expanded && recentlyViewed && recentlyViewed.length > 0 && (
556
585
  <div className='flex flex-col gap-3'>
557
586
  <h4 className='text-sm font-medium'>{recentlyViewedLabel}</h4>
558
587
  <div className='grid grid-cols-3 gap-2'>
559
- {recentlyViewed.map(item => (
560
- <button
561
- key={item.id}
562
- type='button'
563
- className={cn(
564
- 'group text-start',
565
- onRecentlyViewedClick && 'cursor-pointer',
566
- )}
567
- onClick={onRecentlyViewedClick ? () => onRecentlyViewedClick(item.id) : undefined}
568
- >
569
- <div className='bg-muted mb-1 aspect-square overflow-hidden'>
570
- {item.image ? (
571
- <img
572
- src={item.image || undefined}
573
- alt={item.name}
574
- className='size-full rounded-md object-cover'
575
- />
588
+ {recentlyViewed.map(item => {
589
+ const suggestionPrice = money(item.unitPrice)
590
+ return (
591
+ <button
592
+ key={item.id}
593
+ type='button'
594
+ className={cn(
595
+ 'group text-start',
596
+ onRecentlyViewedClick && 'cursor-pointer',
597
+ )}
598
+ onClick={onRecentlyViewedClick ? () => onRecentlyViewedClick(item.id) : undefined}
599
+ >
600
+ <div className='bg-muted mb-1 aspect-square overflow-hidden'>
601
+ {item.image ? (
602
+ <img
603
+ src={item.image || undefined}
604
+ alt={item.name}
605
+ className='size-full rounded-md object-cover'
606
+ />
607
+ ) : null}
608
+ </div>
609
+ <p className='text-muted-foreground truncate text-xs'>{item.name}</p>
610
+ {suggestionPrice !== null ? (
611
+ <p className='text-xs font-medium tabular-nums'>{suggestionPrice}</p>
576
612
  ) : null}
577
- </div>
578
- <p className='text-muted-foreground truncate text-xs'>{item.name}</p>
579
- <p className='text-xs font-medium'>${item.price.toFixed(2)}</p>
580
- </button>
581
- ))}
613
+ </button>
614
+ )
615
+ })}
582
616
  </div>
583
617
  </div>
584
618
  )}
@@ -591,4 +625,4 @@ export function ShoppingCart4({
591
625
  )
592
626
  }
593
627
 
594
- export default ShoppingCart4
628
+ export default ShoppingCart