@ecomiq/storefront 2.0.11 → 2.0.14
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/bin/ecomiq.mjs +3 -1
- package/package.json +4 -2
- package/src/auth/create-storefront-auth.ts +65 -6
- package/src/auth/ecomiq-oauth-password-constants.ts +1 -3
- package/src/auth/ecomiq-oauth-password-plugin.ts +12 -17
- package/src/auth/ecomiq-storefront-sign-in.ts +0 -3
- package/src/auth/secure-cookies.ts +23 -0
- package/src/auth/storefront-auth-base-url.ts +117 -0
- package/src/codegen.mjs +27 -1
- package/src/components/account/customer-addresses-panel.tsx +678 -0
- package/src/components/account/customer-order-detail-panel.tsx +285 -0
- package/src/components/account/customer-orders-panel.tsx +192 -0
- package/src/components/account/customer-profile-panel.tsx +379 -0
- package/src/components/checkout/checkout-contact-section.tsx +17 -11
- package/src/components/checkout/checkout-session-view.tsx +83 -2
- package/src/components/checkout/checkout-shipping-address-section.tsx +107 -2
- package/src/components/checkout/checkout-steps.ts +15 -0
- package/src/components/checkout/map-customer-to-checkout.ts +188 -0
- package/src/components/checkout/use-checkout-customer-prefill.ts +409 -0
- package/src/components/checkout/use-checkout-session.ts +5 -4
- package/src/config.d.ts +4 -0
- package/src/config.mjs +14 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/recently-viewed.ts +125 -0
- package/src/index.ts +63 -0
- package/src/pages/account/account-page-skeleton.tsx +69 -0
- package/src/pages/account/account-page-view.tsx +270 -0
- package/src/pages/account/account-shell.tsx +101 -0
- package/src/pages/account/parse-account-route.ts +36 -0
- package/src/pages/account.tsx +16 -0
- package/src/pages/checkout.$checkoutId.tsx +1 -1
- package/src/providers/auth.tsx +11 -2
- package/src/proxy-api.ts +20 -0
- package/src/vite-inline.mjs +54 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { ArrowLeft, CreditCard, Loader2, MapPin, Package } from 'lucide-react'
|
|
2
|
+
import {
|
|
3
|
+
customerOrderStatusTone,
|
|
4
|
+
formatCustomerOrderStatus,
|
|
5
|
+
formatOrderDate,
|
|
6
|
+
formatOrderDateTime,
|
|
7
|
+
type CustomerOrderDetail,
|
|
8
|
+
} from '@ecomiq/core'
|
|
9
|
+
import { Link } from '../../link'
|
|
10
|
+
import { buttonVariants, cn } from '../../ui'
|
|
11
|
+
import { formatMoney } from '../checkout/format'
|
|
12
|
+
|
|
13
|
+
function statusBadgeClass(
|
|
14
|
+
tone: ReturnType<typeof customerOrderStatusTone>,
|
|
15
|
+
): string {
|
|
16
|
+
switch (tone) {
|
|
17
|
+
case 'success':
|
|
18
|
+
return 'bg-emerald-50 text-emerald-800 ring-emerald-200'
|
|
19
|
+
case 'warning':
|
|
20
|
+
return 'bg-amber-50 text-amber-900 ring-amber-200'
|
|
21
|
+
case 'danger':
|
|
22
|
+
return 'bg-red-50 text-red-800 ring-red-200'
|
|
23
|
+
case 'info':
|
|
24
|
+
return 'bg-sky-50 text-sky-900 ring-sky-200'
|
|
25
|
+
default:
|
|
26
|
+
return 'bg-muted text-foreground ring-border'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type CustomerOrderDetailPanelProps = {
|
|
31
|
+
order?: CustomerOrderDetail | null
|
|
32
|
+
isLoading: boolean
|
|
33
|
+
errorMessage?: string | null
|
|
34
|
+
backHref: string
|
|
35
|
+
/** Genera URL de producto si hay handle (p. ej. `/products/{handle}`). */
|
|
36
|
+
productHref?: (handle: string) => string
|
|
37
|
+
language?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function CustomerOrderDetailPanel({
|
|
41
|
+
order,
|
|
42
|
+
isLoading,
|
|
43
|
+
errorMessage,
|
|
44
|
+
backHref,
|
|
45
|
+
productHref,
|
|
46
|
+
language = 'es-PE',
|
|
47
|
+
}: CustomerOrderDetailPanelProps) {
|
|
48
|
+
if (isLoading) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-background px-4 py-10 text-sm text-muted-foreground">
|
|
51
|
+
<Loader2 className="size-4 shrink-0 animate-spin" />
|
|
52
|
+
Cargando detalle del pedido…
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (errorMessage) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-4">
|
|
60
|
+
<Link
|
|
61
|
+
to={backHref}
|
|
62
|
+
className={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'gap-2')}
|
|
63
|
+
>
|
|
64
|
+
<ArrowLeft className="size-4" />
|
|
65
|
+
Volver a mis pedidos
|
|
66
|
+
</Link>
|
|
67
|
+
<p className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
|
68
|
+
{errorMessage}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!order) return null
|
|
75
|
+
|
|
76
|
+
const tone = customerOrderStatusTone(order.status)
|
|
77
|
+
const timeline = [...order.statusTimeline].sort(
|
|
78
|
+
(a, b) =>
|
|
79
|
+
new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime(),
|
|
80
|
+
)
|
|
81
|
+
const money = (value: number) =>
|
|
82
|
+
formatMoney(value, language, order.currencyCode)
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="space-y-6">
|
|
86
|
+
<Link
|
|
87
|
+
to={backHref}
|
|
88
|
+
className={cn(
|
|
89
|
+
buttonVariants({ variant: 'ghost', size: 'sm' }),
|
|
90
|
+
'-ml-2 gap-2 text-muted-foreground',
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<ArrowLeft className="size-4" />
|
|
94
|
+
Volver a mis pedidos
|
|
95
|
+
</Link>
|
|
96
|
+
|
|
97
|
+
<div className="rounded-lg border border-border bg-background p-5 sm:p-6">
|
|
98
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
99
|
+
<div>
|
|
100
|
+
<p className="text-sm text-muted-foreground">
|
|
101
|
+
Pedido del {formatOrderDate(order.orderDate)}
|
|
102
|
+
</p>
|
|
103
|
+
<h2 className="mt-1 text-xl font-semibold text-foreground sm:text-2xl">
|
|
104
|
+
Pedido n.º {order.orderNumber}
|
|
105
|
+
</h2>
|
|
106
|
+
</div>
|
|
107
|
+
<span
|
|
108
|
+
className={cn(
|
|
109
|
+
'inline-flex rounded-full px-3 py-1 text-sm font-medium ring-1 ring-inset',
|
|
110
|
+
statusBadgeClass(tone),
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
{formatCustomerOrderStatus(order.status)}
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="mt-6 grid gap-6 lg:grid-cols-3">
|
|
118
|
+
<div className="space-y-6 lg:col-span-2">
|
|
119
|
+
<section>
|
|
120
|
+
<h3 className="text-base font-semibold text-foreground">Productos</h3>
|
|
121
|
+
<ul className="mt-3 divide-y divide-border rounded-lg border border-border">
|
|
122
|
+
{order.items.map((item) => {
|
|
123
|
+
const href =
|
|
124
|
+
item.productHandle && productHref
|
|
125
|
+
? productHref(item.productHandle)
|
|
126
|
+
: null
|
|
127
|
+
return (
|
|
128
|
+
<li key={item.id} className="flex gap-4 p-4">
|
|
129
|
+
<div className="size-20 shrink-0 overflow-hidden rounded-md border border-border bg-muted/40">
|
|
130
|
+
{item.thumbnail ? (
|
|
131
|
+
<img
|
|
132
|
+
src={item.thumbnail}
|
|
133
|
+
alt=""
|
|
134
|
+
className="size-full object-cover"
|
|
135
|
+
/>
|
|
136
|
+
) : (
|
|
137
|
+
<div className="flex size-full items-center justify-center text-muted-foreground">
|
|
138
|
+
<Package className="size-8" />
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
<div className="min-w-0 flex-1">
|
|
143
|
+
{href ? (
|
|
144
|
+
<Link
|
|
145
|
+
to={href}
|
|
146
|
+
className="font-medium text-primary hover:underline"
|
|
147
|
+
>
|
|
148
|
+
{item.title}
|
|
149
|
+
</Link>
|
|
150
|
+
) : (
|
|
151
|
+
<p className="font-medium text-foreground">{item.title}</p>
|
|
152
|
+
)}
|
|
153
|
+
{item.variantTitle ? (
|
|
154
|
+
<p className="text-sm text-muted-foreground">
|
|
155
|
+
{item.variantTitle}
|
|
156
|
+
</p>
|
|
157
|
+
) : null}
|
|
158
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
159
|
+
Cantidad: {item.quantity}
|
|
160
|
+
</p>
|
|
161
|
+
<p className="mt-1 text-sm font-medium text-foreground">
|
|
162
|
+
{money(item.total)}
|
|
163
|
+
</p>
|
|
164
|
+
</div>
|
|
165
|
+
</li>
|
|
166
|
+
)
|
|
167
|
+
})}
|
|
168
|
+
</ul>
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
{timeline.length > 0 ? (
|
|
172
|
+
<section>
|
|
173
|
+
<h3 className="text-base font-semibold text-foreground">
|
|
174
|
+
Seguimiento
|
|
175
|
+
</h3>
|
|
176
|
+
<ol className="mt-3 space-y-0 border-l-2 border-primary/30 pl-4">
|
|
177
|
+
{timeline.map((entry, index) => (
|
|
178
|
+
<li
|
|
179
|
+
key={`${entry.key}-${entry.occurredAt}`}
|
|
180
|
+
className="relative pb-6 last:pb-0"
|
|
181
|
+
>
|
|
182
|
+
<span
|
|
183
|
+
className={cn(
|
|
184
|
+
'absolute -left-[21px] top-1 size-2.5 rounded-full ring-2 ring-background',
|
|
185
|
+
index === 0 ? 'bg-primary' : 'bg-muted-foreground/40',
|
|
186
|
+
)}
|
|
187
|
+
/>
|
|
188
|
+
<p className="text-sm font-medium text-foreground">
|
|
189
|
+
{entry.label}
|
|
190
|
+
</p>
|
|
191
|
+
<p className="text-xs text-muted-foreground">
|
|
192
|
+
{formatOrderDateTime(entry.occurredAt)}
|
|
193
|
+
</p>
|
|
194
|
+
{entry.description ? (
|
|
195
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
196
|
+
{entry.description}
|
|
197
|
+
</p>
|
|
198
|
+
) : null}
|
|
199
|
+
</li>
|
|
200
|
+
))}
|
|
201
|
+
</ol>
|
|
202
|
+
</section>
|
|
203
|
+
) : null}
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div className="space-y-4">
|
|
207
|
+
<section className="rounded-lg border border-border bg-muted/30 p-4">
|
|
208
|
+
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground">
|
|
209
|
+
<MapPin className="size-4" />
|
|
210
|
+
Dirección de envío
|
|
211
|
+
</h3>
|
|
212
|
+
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
213
|
+
{order.shippingAddressSummary ??
|
|
214
|
+
[
|
|
215
|
+
order.shippingAddress?.address1,
|
|
216
|
+
order.shippingAddress?.address2,
|
|
217
|
+
order.shippingAddress?.district,
|
|
218
|
+
order.shippingAddress?.province,
|
|
219
|
+
order.shippingAddress?.postalCode,
|
|
220
|
+
]
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
.join(', ')}
|
|
223
|
+
</p>
|
|
224
|
+
{order.shippingAddress?.phone ? (
|
|
225
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
226
|
+
{order.shippingAddress.phone}
|
|
227
|
+
</p>
|
|
228
|
+
) : null}
|
|
229
|
+
</section>
|
|
230
|
+
|
|
231
|
+
<section className="rounded-lg border border-border bg-muted/30 p-4">
|
|
232
|
+
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground">
|
|
233
|
+
<CreditCard className="size-4" />
|
|
234
|
+
Resumen de pago
|
|
235
|
+
</h3>
|
|
236
|
+
<dl className="mt-3 space-y-2 text-sm">
|
|
237
|
+
<div className="flex justify-between text-muted-foreground">
|
|
238
|
+
<dt>Subtotal</dt>
|
|
239
|
+
<dd>{money(order.subTotal)}</dd>
|
|
240
|
+
</div>
|
|
241
|
+
{order.discount > 0 ? (
|
|
242
|
+
<div className="flex justify-between text-muted-foreground">
|
|
243
|
+
<dt>Descuento</dt>
|
|
244
|
+
<dd className="text-emerald-700">-{money(order.discount)}</dd>
|
|
245
|
+
</div>
|
|
246
|
+
) : null}
|
|
247
|
+
<div className="flex justify-between text-muted-foreground">
|
|
248
|
+
<dt>Envío</dt>
|
|
249
|
+
<dd>{money(order.shipping)}</dd>
|
|
250
|
+
</div>
|
|
251
|
+
{order.tax > 0 ? (
|
|
252
|
+
<div className="flex justify-between text-muted-foreground">
|
|
253
|
+
<dt>Impuestos</dt>
|
|
254
|
+
<dd>{money(order.tax)}</dd>
|
|
255
|
+
</div>
|
|
256
|
+
) : null}
|
|
257
|
+
<div className="flex justify-between border-t border-border pt-2 font-semibold text-foreground">
|
|
258
|
+
<dt>Total</dt>
|
|
259
|
+
<dd>{money(order.total)}</dd>
|
|
260
|
+
</div>
|
|
261
|
+
</dl>
|
|
262
|
+
{order.payments.length > 0 ? (
|
|
263
|
+
<ul className="mt-4 space-y-2 border-t border-border pt-3 text-sm">
|
|
264
|
+
{order.payments.map((p) => (
|
|
265
|
+
<li key={p.id} className="text-muted-foreground">
|
|
266
|
+
<span className="font-medium text-foreground">
|
|
267
|
+
{p.paymentMethod}
|
|
268
|
+
</span>
|
|
269
|
+
{' · '}
|
|
270
|
+
{money(p.amount)}
|
|
271
|
+
<span className="text-muted-foreground">
|
|
272
|
+
{' '}
|
|
273
|
+
({p.paymentStatus})
|
|
274
|
+
</span>
|
|
275
|
+
</li>
|
|
276
|
+
))}
|
|
277
|
+
</ul>
|
|
278
|
+
) : null}
|
|
279
|
+
</section>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { ChevronRight, Loader2, Package } from 'lucide-react'
|
|
2
|
+
import {
|
|
3
|
+
customerOrderStatusTone,
|
|
4
|
+
formatCustomerOrderStatus,
|
|
5
|
+
formatOrderDate,
|
|
6
|
+
type CustomerOrderListItem,
|
|
7
|
+
} from '@ecomiq/core'
|
|
8
|
+
import { Link } from '../../link'
|
|
9
|
+
import { buttonVariants, cn } from '../../ui'
|
|
10
|
+
import { formatMoney } from '../checkout/format'
|
|
11
|
+
|
|
12
|
+
function statusBadgeClass(
|
|
13
|
+
tone: ReturnType<typeof customerOrderStatusTone>,
|
|
14
|
+
): string {
|
|
15
|
+
switch (tone) {
|
|
16
|
+
case 'success':
|
|
17
|
+
return 'bg-emerald-50 text-emerald-800 ring-emerald-200'
|
|
18
|
+
case 'warning':
|
|
19
|
+
return 'bg-amber-50 text-amber-900 ring-amber-200'
|
|
20
|
+
case 'danger':
|
|
21
|
+
return 'bg-red-50 text-red-800 ring-red-200'
|
|
22
|
+
case 'info':
|
|
23
|
+
return 'bg-sky-50 text-sky-900 ring-sky-200'
|
|
24
|
+
default:
|
|
25
|
+
return 'bg-muted text-foreground ring-border'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type CustomerOrdersPanelProps = {
|
|
30
|
+
orders?: CustomerOrderListItem[]
|
|
31
|
+
isLoading: boolean
|
|
32
|
+
errorMessage?: string | null
|
|
33
|
+
/** Prefijo de ruta, p. ej. `/account/orders` → enlace a `{base}/{orderId}`. */
|
|
34
|
+
orderDetailBasePath: string
|
|
35
|
+
shopHref?: string
|
|
36
|
+
language?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function CustomerOrdersPanel({
|
|
40
|
+
orders,
|
|
41
|
+
isLoading,
|
|
42
|
+
errorMessage,
|
|
43
|
+
orderDetailBasePath,
|
|
44
|
+
shopHref = '/',
|
|
45
|
+
language = 'es-PE',
|
|
46
|
+
}: CustomerOrdersPanelProps) {
|
|
47
|
+
const list = orders ?? []
|
|
48
|
+
const base = orderDetailBasePath.replace(/\/$/, '')
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="space-y-6">
|
|
52
|
+
<div>
|
|
53
|
+
<h2 className="text-lg font-medium text-foreground">Mis pedidos</h2>
|
|
54
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
55
|
+
Rastrea envíos, revisa totales y vuelve a ver lo que compraste.
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{isLoading ? (
|
|
60
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-background px-4 py-8 text-sm text-muted-foreground">
|
|
61
|
+
<Loader2 className="size-4 shrink-0 animate-spin" />
|
|
62
|
+
Cargando pedidos…
|
|
63
|
+
</div>
|
|
64
|
+
) : null}
|
|
65
|
+
|
|
66
|
+
{errorMessage && !isLoading ? (
|
|
67
|
+
<p className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
|
68
|
+
{errorMessage}
|
|
69
|
+
</p>
|
|
70
|
+
) : null}
|
|
71
|
+
|
|
72
|
+
{!isLoading && !errorMessage && list.length === 0 ? (
|
|
73
|
+
<div className="rounded-lg border border-dashed border-border bg-muted/30 px-6 py-12 text-center">
|
|
74
|
+
<Package className="mx-auto mb-3 size-12 text-muted-foreground/70" />
|
|
75
|
+
<p className="text-base font-medium text-foreground">
|
|
76
|
+
Aún no tienes pedidos
|
|
77
|
+
</p>
|
|
78
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
79
|
+
Cuando compres en esta tienda, aparecerán aquí con su estado de
|
|
80
|
+
envío.
|
|
81
|
+
</p>
|
|
82
|
+
<Link to={shopHref} className={cn(buttonVariants(), 'mt-6')}>
|
|
83
|
+
Seguir comprando
|
|
84
|
+
</Link>
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
87
|
+
|
|
88
|
+
{!isLoading && list.length > 0 ? (
|
|
89
|
+
<ul className="space-y-4">
|
|
90
|
+
{list.map((order) => {
|
|
91
|
+
const tone = customerOrderStatusTone(order.status)
|
|
92
|
+
const visibleItems = order.items.slice(0, 4)
|
|
93
|
+
const extraCount = Math.max(0, order.items.length - visibleItems.length)
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<li
|
|
97
|
+
key={order.orderId}
|
|
98
|
+
className="overflow-hidden rounded-lg border border-border bg-background"
|
|
99
|
+
>
|
|
100
|
+
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border bg-muted/40 px-4 py-3 sm:px-5">
|
|
101
|
+
<div className="min-w-0 space-y-0.5">
|
|
102
|
+
<p className="text-sm text-muted-foreground">
|
|
103
|
+
Pedido realizado el{' '}
|
|
104
|
+
<span className="font-medium text-foreground">
|
|
105
|
+
{formatOrderDate(order.orderDate)}
|
|
106
|
+
</span>
|
|
107
|
+
</p>
|
|
108
|
+
<p className="text-sm font-semibold text-foreground">
|
|
109
|
+
N.º de pedido: {order.orderNumber}
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
113
|
+
<span
|
|
114
|
+
className={cn(
|
|
115
|
+
'inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ring-1 ring-inset',
|
|
116
|
+
statusBadgeClass(tone),
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
{formatCustomerOrderStatus(order.status)}
|
|
120
|
+
</span>
|
|
121
|
+
<p className="text-base font-semibold text-foreground">
|
|
122
|
+
{formatMoney(order.total, language, order.currencyCode)}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div className="px-4 py-4 sm:px-5">
|
|
128
|
+
<div className="flex flex-wrap gap-3">
|
|
129
|
+
{visibleItems.map((item) => (
|
|
130
|
+
<div
|
|
131
|
+
key={item.id}
|
|
132
|
+
className="flex w-full max-w-[280px] gap-3 sm:w-auto sm:min-w-[220px] sm:flex-1"
|
|
133
|
+
>
|
|
134
|
+
<div className="size-16 shrink-0 overflow-hidden rounded-md border border-border bg-muted/40">
|
|
135
|
+
{item.thumbnail ? (
|
|
136
|
+
<img
|
|
137
|
+
src={item.thumbnail}
|
|
138
|
+
alt=""
|
|
139
|
+
className="size-full object-cover"
|
|
140
|
+
/>
|
|
141
|
+
) : (
|
|
142
|
+
<div className="flex size-full items-center justify-center text-muted-foreground">
|
|
143
|
+
<Package className="size-6" />
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
<div className="min-w-0 flex-1 text-sm">
|
|
148
|
+
<p className="line-clamp-2 font-medium text-foreground">
|
|
149
|
+
{item.title}
|
|
150
|
+
</p>
|
|
151
|
+
{item.variantTitle ? (
|
|
152
|
+
<p className="text-muted-foreground">
|
|
153
|
+
{item.variantTitle}
|
|
154
|
+
</p>
|
|
155
|
+
) : null}
|
|
156
|
+
<p className="text-muted-foreground">
|
|
157
|
+
Cant.: {item.quantity}
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
{extraCount > 0 ? (
|
|
163
|
+
<p className="self-center text-sm text-muted-foreground">
|
|
164
|
+
+{extraCount} producto{extraCount === 1 ? '' : 's'} más
|
|
165
|
+
</p>
|
|
166
|
+
) : null}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{order.shippingAddressSummary ? (
|
|
170
|
+
<p className="mt-3 line-clamp-2 text-xs text-muted-foreground">
|
|
171
|
+
Enviar a: {order.shippingAddressSummary}
|
|
172
|
+
</p>
|
|
173
|
+
) : null}
|
|
174
|
+
|
|
175
|
+
<div className="mt-4 flex flex-wrap gap-2 border-t border-border pt-4">
|
|
176
|
+
<Link
|
|
177
|
+
to={`${base}/${encodeURIComponent(order.orderId)}`}
|
|
178
|
+
className={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'gap-1')}
|
|
179
|
+
>
|
|
180
|
+
Ver detalles del pedido
|
|
181
|
+
<ChevronRight className="size-4" />
|
|
182
|
+
</Link>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</li>
|
|
186
|
+
)
|
|
187
|
+
})}
|
|
188
|
+
</ul>
|
|
189
|
+
) : null}
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|