@tuturuuu/ui 0.7.0 → 0.8.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.
- package/CHANGELOG.md +48 -0
- package/package.json +8 -8
- package/src/components/ui/currency-input.test.tsx +43 -0
- package/src/components/ui/currency-input.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
- package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
- package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
- package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
- package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
- package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
- package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
- package/src/components/ui/finance/transactions/form-types.ts +2 -0
- package/src/components/ui/finance/transactions/transaction-card.tsx +21 -9
- package/src/components/ui/money-input.test.tsx +64 -0
- package/src/components/ui/money-input.tsx +63 -0
- package/src/components/ui/storefront/cart-summary.tsx +114 -29
- package/src/components/ui/storefront/checkout-overlay.tsx +27 -0
- package/src/components/ui/storefront/hero-panel.tsx +2 -8
- package/src/components/ui/storefront/image-panel.tsx +6 -0
- package/src/components/ui/storefront/index.ts +11 -0
- package/src/components/ui/storefront/listing-card.tsx +84 -22
- package/src/components/ui/storefront/product-detail.tsx +289 -0
- package/src/components/ui/storefront/product-dialog.tsx +72 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +124 -1
- package/src/components/ui/storefront/storefront-surface.tsx +333 -133
- package/src/components/ui/storefront/types.ts +23 -1
- package/src/components/ui/storefront/utils.ts +111 -27
- package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
- package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
- package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
- package/src/components/ui/text-editor/content-migration.ts +41 -18
- package/src/components/ui/text-editor/extensions.ts +1 -1
- package/src/components/ui/text-editor/image-extension.ts +40 -18
- package/src/components/ui/text-editor/video-extension.ts +11 -2
- package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
- package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +1 -3
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +13 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +46 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +13 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +3 -1
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +164 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +56 -2
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
- package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +34 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +39 -0
- package/src/components/ui/tu-do/shared/board-views.tsx +9 -7
- package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
- package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +42 -14
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
- package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
- package/src/hooks/useBoardRealtime.ts +6 -3
- package/src/hooks/useBoardRealtime.types.ts +11 -0
- package/src/hooks/useCursorTracking.ts +91 -27
- package/src/hooks/useTaskUserRealtime.ts +5 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ShoppingCart } from '@tuturuuu/icons';
|
|
3
|
+
import { ArrowLeft, ShoppingCart } from '@tuturuuu/icons';
|
|
4
4
|
import type {
|
|
5
5
|
InventoryStorefront,
|
|
6
6
|
InventoryStorefrontListing,
|
|
@@ -9,11 +9,15 @@ import type {
|
|
|
9
9
|
import { cn } from '@tuturuuu/utils/format';
|
|
10
10
|
import type { ReactNode } from 'react';
|
|
11
11
|
import { StorefrontCartSummary } from './cart-summary';
|
|
12
|
+
import { StorefrontCheckoutOverlay } from './checkout-overlay';
|
|
12
13
|
import { StorefrontEmptyListings } from './empty-listings';
|
|
13
14
|
import { StorefrontHeroPanel } from './hero-panel';
|
|
14
15
|
import { StorefrontImagePanel } from './image-panel';
|
|
15
16
|
import { StorefrontListingCard } from './listing-card';
|
|
17
|
+
import { StorefrontProductDetail } from './product-detail';
|
|
18
|
+
import { StorefrontProductDialog } from './product-dialog';
|
|
16
19
|
import type {
|
|
20
|
+
StorefrontBuyerDefaults,
|
|
17
21
|
StorefrontCartLine,
|
|
18
22
|
StorefrontSurfaceLabels,
|
|
19
23
|
StorefrontSurfaceMode,
|
|
@@ -21,7 +25,10 @@ import type {
|
|
|
21
25
|
import { mergeStorefrontSurfaceLabels } from './types';
|
|
22
26
|
import {
|
|
23
27
|
getAccentStyle,
|
|
28
|
+
getSafeStorefrontHttpUrl,
|
|
29
|
+
getStorefrontLinePrice,
|
|
24
30
|
getStorefrontListingLimit,
|
|
31
|
+
getStorefrontVariantLimit,
|
|
25
32
|
sanitizeStorefrontAccentColor,
|
|
26
33
|
storefrontRadiusClasses,
|
|
27
34
|
storefrontSurfaceClasses,
|
|
@@ -29,64 +36,100 @@ import {
|
|
|
29
36
|
} from './utils';
|
|
30
37
|
|
|
31
38
|
export function StorefrontSurface({
|
|
39
|
+
buyerDefaults,
|
|
32
40
|
cartLines = [],
|
|
41
|
+
cartHref,
|
|
33
42
|
checkoutHref,
|
|
34
43
|
className,
|
|
35
44
|
compactLayout = false,
|
|
45
|
+
detailListingId,
|
|
36
46
|
emptyAction,
|
|
37
47
|
headerActions,
|
|
38
48
|
isDemo: _isDemo = false,
|
|
49
|
+
isRedirecting = false,
|
|
39
50
|
isSubmitting = false,
|
|
40
51
|
labels: labelOverrides,
|
|
41
52
|
listings,
|
|
42
53
|
mode,
|
|
43
54
|
notice,
|
|
55
|
+
onBuyNow,
|
|
44
56
|
onCheckoutSubmit,
|
|
45
57
|
onDecrement,
|
|
58
|
+
onDetailListingChange,
|
|
46
59
|
onIncrement,
|
|
60
|
+
onInstantCheckout,
|
|
47
61
|
selectedListingId,
|
|
48
62
|
storefront,
|
|
63
|
+
storefrontHref,
|
|
49
64
|
}: {
|
|
65
|
+
buyerDefaults?: StorefrontBuyerDefaults;
|
|
50
66
|
cartLines?: StorefrontCartLine[];
|
|
67
|
+
cartHref?: string;
|
|
51
68
|
checkoutHref?: string;
|
|
52
69
|
className?: string;
|
|
53
70
|
compactLayout?: boolean;
|
|
71
|
+
detailListingId?: string | null;
|
|
54
72
|
emptyAction?: ReactNode;
|
|
55
73
|
headerActions?: ReactNode;
|
|
56
74
|
isDemo?: boolean;
|
|
75
|
+
isRedirecting?: boolean;
|
|
57
76
|
isSubmitting?: boolean;
|
|
58
77
|
labels?: Partial<StorefrontSurfaceLabels>;
|
|
59
78
|
listings: InventoryStorefrontListing[];
|
|
60
79
|
mode: StorefrontSurfaceMode;
|
|
61
80
|
notice?: ReactNode;
|
|
81
|
+
onBuyNow?: (listingId: string, variantId?: string | null) => void;
|
|
62
82
|
onCheckoutSubmit?: (formData: FormData) => void;
|
|
63
|
-
onDecrement?: (listingId: string) => void;
|
|
64
|
-
|
|
83
|
+
onDecrement?: (listingId: string, variantId?: string | null) => void;
|
|
84
|
+
onDetailListingChange?: (listingId: string | null) => void;
|
|
85
|
+
onIncrement?: (
|
|
86
|
+
listingId: string,
|
|
87
|
+
maxQuantity: number,
|
|
88
|
+
variantId?: string | null
|
|
89
|
+
) => void;
|
|
90
|
+
onInstantCheckout?: () => void;
|
|
65
91
|
selectedListingId?: string;
|
|
66
92
|
storefront: InventoryStorefront;
|
|
93
|
+
storefrontHref?: string;
|
|
67
94
|
}) {
|
|
68
95
|
const labels = mergeStorefrontSurfaceLabels(labelOverrides);
|
|
69
96
|
const accentColor = sanitizeStorefrontAccentColor(storefront.accentColor);
|
|
70
97
|
const radius = storefrontRadiusClasses[storefront.cornerStyle];
|
|
98
|
+
const resolveVariant = (
|
|
99
|
+
listing: InventoryStorefrontListing,
|
|
100
|
+
variantId?: string | null
|
|
101
|
+
) =>
|
|
102
|
+
variantId
|
|
103
|
+
? (listing.variants ?? []).find((variant) => variant.id === variantId)
|
|
104
|
+
: undefined;
|
|
71
105
|
const cartEntries = cartLines.flatMap((line) => {
|
|
72
106
|
const listing = listings.find((item) => item.id === line.listingId);
|
|
73
|
-
|
|
107
|
+
if (!listing) return [];
|
|
108
|
+
return [
|
|
109
|
+
{ line, listing, variant: resolveVariant(listing, line.variantId) },
|
|
110
|
+
];
|
|
74
111
|
});
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
getStorefrontListingLimit(listing)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const total = checkoutEntries.reduce((sum,
|
|
83
|
-
const quantity = Math.min(
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
const lineLimit = (entry: (typeof cartEntries)[number]) =>
|
|
113
|
+
entry.variant
|
|
114
|
+
? getStorefrontVariantLimit(entry.listing, entry.variant)
|
|
115
|
+
: getStorefrontListingLimit(entry.listing);
|
|
116
|
+
const checkoutEntries = cartEntries.filter(
|
|
117
|
+
(entry) => Math.min(entry.line.quantity, lineLimit(entry)) > 0
|
|
118
|
+
);
|
|
119
|
+
const total = checkoutEntries.reduce((sum, entry) => {
|
|
120
|
+
const quantity = Math.min(entry.line.quantity, lineLimit(entry));
|
|
121
|
+
return (
|
|
122
|
+
sum + getStorefrontLinePrice(entry.listing, entry.variant) * quantity
|
|
86
123
|
);
|
|
87
|
-
return sum + listing.price * quantity;
|
|
88
124
|
}, 0);
|
|
89
125
|
const cartQuantity = cartLines.reduce((sum, line) => sum + line.quantity, 0);
|
|
126
|
+
const detailListing = detailListingId
|
|
127
|
+
? listings.find((listing) => listing.id === detailListingId)
|
|
128
|
+
: undefined;
|
|
129
|
+
const selectedListing = selectedListingId
|
|
130
|
+
? listings.find((listing) => listing.id === selectedListingId)
|
|
131
|
+
: undefined;
|
|
132
|
+
const isProductDetail = mode === 'product' && Boolean(selectedListing);
|
|
90
133
|
const visibleListings =
|
|
91
134
|
mode === 'product' && selectedListingId
|
|
92
135
|
? listings.filter((listing) => listing.id === selectedListingId)
|
|
@@ -99,6 +142,42 @@ export function StorefrontSurface({
|
|
|
99
142
|
: visibleListings;
|
|
100
143
|
const currency = storefront.currency ?? 'USD';
|
|
101
144
|
|
|
145
|
+
const cartSummary = (
|
|
146
|
+
<StorefrontCartSummary
|
|
147
|
+
buyerDefaults={buyerDefaults}
|
|
148
|
+
cartEntries={checkoutEntries}
|
|
149
|
+
checkoutHref={checkoutHref}
|
|
150
|
+
currency={currency}
|
|
151
|
+
isCheckout={isCheckout}
|
|
152
|
+
isPreview={isPreview}
|
|
153
|
+
isSubmitting={isSubmitting}
|
|
154
|
+
labels={labels}
|
|
155
|
+
onCheckoutSubmit={onCheckoutSubmit}
|
|
156
|
+
onInstantCheckout={mode === 'cart' ? onInstantCheckout : undefined}
|
|
157
|
+
radius={radius}
|
|
158
|
+
storefront={storefront}
|
|
159
|
+
total={total}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
const cartControlClassName = cn(
|
|
163
|
+
'inline-flex h-11 min-w-14 shrink-0 items-center justify-center gap-2 border bg-card px-3 font-semibold text-sm tabular-nums transition hover:bg-muted/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',
|
|
164
|
+
radius
|
|
165
|
+
);
|
|
166
|
+
const cartControlStyle =
|
|
167
|
+
cartQuantity > 0
|
|
168
|
+
? {
|
|
169
|
+
borderColor: 'var(--storefront-accent, var(--primary))',
|
|
170
|
+
color: 'var(--storefront-accent, var(--primary))',
|
|
171
|
+
}
|
|
172
|
+
: undefined;
|
|
173
|
+
const cartControlContent = (
|
|
174
|
+
<>
|
|
175
|
+
<ShoppingCart aria-hidden className="size-5 shrink-0" />
|
|
176
|
+
<span className="sr-only">{labels.cart}: </span>
|
|
177
|
+
<span className="min-w-4 text-center">{cartQuantity}</span>
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
|
|
102
181
|
return (
|
|
103
182
|
<main
|
|
104
183
|
className={cn(
|
|
@@ -108,17 +187,34 @@ export function StorefrontSurface({
|
|
|
108
187
|
)}
|
|
109
188
|
style={getAccentStyle(accentColor)}
|
|
110
189
|
>
|
|
190
|
+
{/* Accent strip — makes the storefront's accent color immediately visible. */}
|
|
191
|
+
<div
|
|
192
|
+
className="h-1 w-full"
|
|
193
|
+
style={{
|
|
194
|
+
backgroundColor: 'var(--storefront-accent, var(--primary))',
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
111
197
|
{notice ? (
|
|
112
198
|
<div className="border-border border-b bg-muted/35 px-4 py-2 text-center text-muted-foreground text-sm">
|
|
113
199
|
{notice}
|
|
114
200
|
</div>
|
|
115
201
|
) : null}
|
|
116
202
|
|
|
117
|
-
<header className="border-border border-b bg-background/
|
|
203
|
+
<header className="sticky top-0 z-30 border-border border-b bg-background/80 backdrop-blur-md supports-[backdrop-filter]:bg-background/65">
|
|
118
204
|
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-3 px-4 py-3">
|
|
119
205
|
<div className="min-w-0">
|
|
120
206
|
<h1 className="truncate font-semibold text-xl">
|
|
121
|
-
{
|
|
207
|
+
{storefrontHref ? (
|
|
208
|
+
<a
|
|
209
|
+
className="block truncate rounded-sm transition hover:text-[var(--storefront-accent,var(--primary))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
|
|
210
|
+
href={storefrontHref}
|
|
211
|
+
title={storefront.name}
|
|
212
|
+
>
|
|
213
|
+
{storefront.name}
|
|
214
|
+
</a>
|
|
215
|
+
) : (
|
|
216
|
+
storefront.name
|
|
217
|
+
)}
|
|
122
218
|
</h1>
|
|
123
219
|
{storefront.description ? (
|
|
124
220
|
<p className="mt-0.5 line-clamp-1 text-muted-foreground text-sm">
|
|
@@ -128,98 +224,198 @@ export function StorefrontSurface({
|
|
|
128
224
|
</div>
|
|
129
225
|
<div className="flex items-center gap-2">
|
|
130
226
|
{headerActions}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
227
|
+
{cartHref ? (
|
|
228
|
+
<a
|
|
229
|
+
aria-label={`${labels.cart}: ${cartQuantity}`}
|
|
230
|
+
className={cartControlClassName}
|
|
231
|
+
href={cartHref}
|
|
232
|
+
style={cartControlStyle}
|
|
233
|
+
>
|
|
234
|
+
{cartControlContent}
|
|
235
|
+
</a>
|
|
236
|
+
) : (
|
|
237
|
+
<span className={cartControlClassName} style={cartControlStyle}>
|
|
238
|
+
{cartControlContent}
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
140
241
|
</div>
|
|
141
242
|
</div>
|
|
142
243
|
</header>
|
|
143
244
|
|
|
144
|
-
|
|
145
|
-
className=
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
245
|
+
{isCheckout ? (
|
|
246
|
+
<section className="mx-auto w-full max-w-xl px-4 py-8">
|
|
247
|
+
{cartHref ? (
|
|
248
|
+
<a
|
|
249
|
+
className="mb-4 inline-flex items-center gap-1.5 text-muted-foreground text-sm transition hover:text-foreground"
|
|
250
|
+
href={cartHref}
|
|
251
|
+
>
|
|
252
|
+
<ArrowLeft aria-hidden className="size-4" />
|
|
253
|
+
{labels.cart}
|
|
254
|
+
</a>
|
|
255
|
+
) : null}
|
|
256
|
+
<h2 className="mb-4 font-semibold text-2xl tracking-tight">
|
|
257
|
+
{labels.checkout}
|
|
258
|
+
</h2>
|
|
259
|
+
{cartSummary}
|
|
260
|
+
</section>
|
|
261
|
+
) : (
|
|
262
|
+
<section
|
|
263
|
+
className={cn(
|
|
264
|
+
'mx-auto grid max-w-7xl gap-4 px-4 py-5',
|
|
265
|
+
compactLayout ? 'grid-cols-1' : 'lg:grid-cols-[minmax(0,1fr)_340px]'
|
|
266
|
+
)}
|
|
267
|
+
>
|
|
268
|
+
<div className="min-w-0">
|
|
269
|
+
{isProductDetail && selectedListing ? (
|
|
270
|
+
<>
|
|
271
|
+
{storefrontHref ? (
|
|
272
|
+
<a
|
|
273
|
+
className="mb-4 inline-flex items-center gap-1.5 text-muted-foreground text-sm transition hover:text-foreground"
|
|
274
|
+
href={storefrontHref}
|
|
275
|
+
>
|
|
276
|
+
<ArrowLeft aria-hidden className="size-4" />
|
|
277
|
+
{labels.browse}
|
|
278
|
+
</a>
|
|
279
|
+
) : null}
|
|
280
|
+
<StorefrontProductDetail
|
|
281
|
+
cartHref={cartHref}
|
|
282
|
+
cartLines={cartLines}
|
|
283
|
+
currency={currency}
|
|
284
|
+
isSubmitting={isSubmitting}
|
|
285
|
+
labels={labels}
|
|
286
|
+
listing={selectedListing}
|
|
287
|
+
onBuyNow={onBuyNow}
|
|
288
|
+
onDecrement={onDecrement}
|
|
289
|
+
onIncrement={onIncrement}
|
|
290
|
+
quantity={
|
|
291
|
+
cartLines.find(
|
|
292
|
+
(item) => item.listingId === selectedListing.id
|
|
293
|
+
)?.quantity ?? 0
|
|
294
|
+
}
|
|
295
|
+
radius={radius}
|
|
296
|
+
showInventoryBadges={storefront.showInventoryBadges}
|
|
297
|
+
surfaceClassName={
|
|
298
|
+
storefrontSurfaceClasses[storefront.surfaceStyle]
|
|
299
|
+
}
|
|
300
|
+
/>
|
|
301
|
+
</>
|
|
302
|
+
) : (
|
|
303
|
+
<>
|
|
304
|
+
<StorefrontHeroPanel
|
|
305
|
+
currency={currency}
|
|
306
|
+
labels={labels}
|
|
307
|
+
listingsCount={listings.length}
|
|
308
|
+
radius={radius}
|
|
309
|
+
storefront={storefront}
|
|
310
|
+
/>
|
|
158
311
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
312
|
+
<StorefrontMerchSections
|
|
313
|
+
radius={radius}
|
|
314
|
+
sections={storefront.sections ?? []}
|
|
315
|
+
/>
|
|
163
316
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
317
|
+
<div
|
|
318
|
+
className={cn(
|
|
319
|
+
'mt-4 grid gap-4',
|
|
320
|
+
compactLayout
|
|
321
|
+
? 'sm:grid-cols-2'
|
|
322
|
+
: 'sm:grid-cols-2 xl:grid-cols-3'
|
|
323
|
+
)}
|
|
324
|
+
>
|
|
325
|
+
{listingRows.length === 0 ? (
|
|
326
|
+
showCartListings ? (
|
|
327
|
+
<div
|
|
328
|
+
className={cn(
|
|
329
|
+
'col-span-full grid min-h-56 place-items-center border border-dashed bg-muted/25 p-6 text-center',
|
|
330
|
+
radius
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
333
|
+
<div className="max-w-sm">
|
|
334
|
+
<ShoppingCart
|
|
335
|
+
aria-hidden
|
|
336
|
+
className="mx-auto size-8 text-muted-foreground"
|
|
337
|
+
/>
|
|
338
|
+
<p className="mt-3 font-semibold">
|
|
339
|
+
{labels.emptyCart}
|
|
340
|
+
</p>
|
|
341
|
+
{storefrontHref ? (
|
|
342
|
+
<a
|
|
343
|
+
className="mt-4 inline-flex items-center gap-1.5 font-medium text-[var(--storefront-accent,var(--primary))] text-sm hover:underline"
|
|
344
|
+
href={storefrontHref}
|
|
345
|
+
>
|
|
346
|
+
<ArrowLeft aria-hidden className="size-4" />
|
|
347
|
+
{labels.browse}
|
|
348
|
+
</a>
|
|
349
|
+
) : null}
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
) : (
|
|
353
|
+
<StorefrontEmptyListings
|
|
354
|
+
action={emptyAction}
|
|
355
|
+
labels={labels}
|
|
356
|
+
radius={radius}
|
|
357
|
+
/>
|
|
358
|
+
)
|
|
359
|
+
) : (
|
|
360
|
+
listingRows.map((listing) => {
|
|
361
|
+
const line = cartLines.find(
|
|
362
|
+
(item) => item.listingId === listing.id
|
|
363
|
+
);
|
|
186
364
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
365
|
+
return (
|
|
366
|
+
<StorefrontListingCard
|
|
367
|
+
currency={currency}
|
|
368
|
+
isList={false}
|
|
369
|
+
key={listing.id}
|
|
370
|
+
labels={labels}
|
|
371
|
+
listing={listing}
|
|
372
|
+
onDecrement={onDecrement}
|
|
373
|
+
onIncrement={onIncrement}
|
|
374
|
+
onOpenDetail={
|
|
375
|
+
onDetailListingChange
|
|
376
|
+
? (id) => onDetailListingChange(id)
|
|
377
|
+
: undefined
|
|
378
|
+
}
|
|
379
|
+
quantity={line?.quantity ?? 0}
|
|
380
|
+
radius={radius}
|
|
381
|
+
showInventoryBadges={storefront.showInventoryBadges}
|
|
382
|
+
surfaceClassName={
|
|
383
|
+
storefrontSurfaceClasses[storefront.surfaceStyle]
|
|
384
|
+
}
|
|
385
|
+
/>
|
|
386
|
+
);
|
|
387
|
+
})
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
</>
|
|
205
391
|
)}
|
|
206
392
|
</div>
|
|
207
|
-
</div>
|
|
208
393
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
394
|
+
{cartSummary}
|
|
395
|
+
</section>
|
|
396
|
+
)}
|
|
397
|
+
|
|
398
|
+
<StorefrontProductDialog
|
|
399
|
+
cartHref={cartHref}
|
|
400
|
+
cartLines={cartLines}
|
|
401
|
+
currency={currency}
|
|
402
|
+
isSubmitting={isSubmitting}
|
|
403
|
+
labels={labels}
|
|
404
|
+
listing={detailListing ?? null}
|
|
405
|
+
onBuyNow={onBuyNow}
|
|
406
|
+
onDecrement={onDecrement}
|
|
407
|
+
onIncrement={onIncrement}
|
|
408
|
+
onOpenChange={(open) => {
|
|
409
|
+
if (!open) onDetailListingChange?.(null);
|
|
410
|
+
}}
|
|
411
|
+
radius={radius}
|
|
412
|
+
showInventoryBadges={storefront.showInventoryBadges}
|
|
413
|
+
surfaceClassName={storefrontSurfaceClasses[storefront.surfaceStyle]}
|
|
414
|
+
/>
|
|
415
|
+
|
|
416
|
+
{isSubmitting || isRedirecting ? (
|
|
417
|
+
<StorefrontCheckoutOverlay label={labels.redirectingToCheckout} />
|
|
418
|
+
) : null}
|
|
223
419
|
</main>
|
|
224
420
|
);
|
|
225
421
|
}
|
|
@@ -240,39 +436,43 @@ function StorefrontMerchSections({
|
|
|
240
436
|
|
|
241
437
|
return (
|
|
242
438
|
<div className="mt-4 grid gap-3">
|
|
243
|
-
{visibleSections.map((section) =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{section.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
439
|
+
{visibleSections.map((section) => {
|
|
440
|
+
const sectionHref = getSafeStorefrontHttpUrl(section.href);
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<section
|
|
444
|
+
className={cn(
|
|
445
|
+
'grid overflow-hidden border border-border bg-card md:grid-cols-[minmax(0,1fr)_280px]',
|
|
446
|
+
radius
|
|
447
|
+
)}
|
|
448
|
+
key={section.id}
|
|
449
|
+
>
|
|
450
|
+
<div className="flex min-w-0 flex-col justify-center gap-2 p-4">
|
|
451
|
+
{section.title ? (
|
|
452
|
+
<h2 className="font-semibold text-lg">{section.title}</h2>
|
|
453
|
+
) : null}
|
|
454
|
+
{section.description ? (
|
|
455
|
+
<p className="text-muted-foreground text-sm leading-6">
|
|
456
|
+
{section.description}
|
|
457
|
+
</p>
|
|
458
|
+
) : null}
|
|
459
|
+
{sectionHref ? (
|
|
460
|
+
<a
|
|
461
|
+
className="mt-1 w-fit font-medium text-sm underline-offset-4 hover:underline"
|
|
462
|
+
href={sectionHref}
|
|
463
|
+
>
|
|
464
|
+
{sectionHref.replace(/^https?:\/\//u, '')}
|
|
465
|
+
</a>
|
|
466
|
+
) : null}
|
|
467
|
+
</div>
|
|
468
|
+
<StorefrontImagePanel
|
|
469
|
+
className="min-h-36 md:min-h-full"
|
|
470
|
+
imageUrl={section.imageUrl}
|
|
471
|
+
label={section.title ?? 'Storefront section'}
|
|
472
|
+
/>
|
|
473
|
+
</section>
|
|
474
|
+
);
|
|
475
|
+
})}
|
|
276
476
|
</div>
|
|
277
477
|
);
|
|
278
478
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
InventoryListingVariant,
|
|
3
|
+
InventoryStorefrontListing,
|
|
4
|
+
} from '@tuturuuu/internal-api/inventory';
|
|
2
5
|
|
|
3
6
|
export type StorefrontCartLine = {
|
|
4
7
|
listingId: string;
|
|
8
|
+
variantId?: string | null;
|
|
5
9
|
quantity: number;
|
|
6
10
|
};
|
|
7
11
|
|
|
8
12
|
export type StorefrontCartEntry = {
|
|
9
13
|
line: StorefrontCartLine;
|
|
10
14
|
listing: InventoryStorefrontListing;
|
|
15
|
+
variant?: InventoryListingVariant;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type StorefrontBuyerDefaults = {
|
|
19
|
+
email?: string | null;
|
|
20
|
+
name?: string | null;
|
|
11
21
|
};
|
|
12
22
|
|
|
13
23
|
export type StorefrontSurfaceMode =
|
|
@@ -22,6 +32,7 @@ export type StorefrontSurfaceLabels = {
|
|
|
22
32
|
available: string;
|
|
23
33
|
browse: string;
|
|
24
34
|
bundle: string;
|
|
35
|
+
buyNow: string;
|
|
25
36
|
cart: string;
|
|
26
37
|
checkout: string;
|
|
27
38
|
checkoutDisabled: string;
|
|
@@ -29,6 +40,11 @@ export type StorefrontSurfaceLabels = {
|
|
|
29
40
|
couponNote: string;
|
|
30
41
|
demoBadge: string;
|
|
31
42
|
emptyCart: string;
|
|
43
|
+
fromPrice: string;
|
|
44
|
+
instantCheckout: string;
|
|
45
|
+
redirectingToCheckout: string;
|
|
46
|
+
selectOptions: string;
|
|
47
|
+
viewDetails: string;
|
|
32
48
|
emptyListingsDescription: string;
|
|
33
49
|
emptyListingsTitle: string;
|
|
34
50
|
fallbackDescription: string;
|
|
@@ -56,6 +72,7 @@ export const defaultStorefrontSurfaceLabels: StorefrontSurfaceLabels = {
|
|
|
56
72
|
available: 'available',
|
|
57
73
|
browse: 'Browse',
|
|
58
74
|
bundle: 'Bundle',
|
|
75
|
+
buyNow: 'Buy now',
|
|
59
76
|
cart: 'Cart',
|
|
60
77
|
checkout: 'Checkout',
|
|
61
78
|
checkoutDisabled: 'Checkout is disabled in preview',
|
|
@@ -63,6 +80,11 @@ export const defaultStorefrontSurfaceLabels: StorefrontSurfaceLabels = {
|
|
|
63
80
|
couponNote: 'Have a coupon? You can apply it at checkout.',
|
|
64
81
|
demoBadge: 'Demo',
|
|
65
82
|
emptyCart: 'Add a listing to start checkout.',
|
|
83
|
+
fromPrice: 'From',
|
|
84
|
+
instantCheckout: 'Instant checkout',
|
|
85
|
+
redirectingToCheckout: 'Taking you to secure checkout…',
|
|
86
|
+
selectOptions: 'Select options',
|
|
87
|
+
viewDetails: 'View details',
|
|
66
88
|
emptyListingsDescription:
|
|
67
89
|
'Publish a listing to make this storefront ready for buyers.',
|
|
68
90
|
emptyListingsTitle: 'No listings yet',
|