@aglyn/plugins-commerce 1.0.0-beta.219 → 1.0.0-beta.221
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/README.md +5 -0
- package/package.json +14 -14
- package/src/lib/components/console/catalog-organization-card.component.js +626 -446
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +1127 -846
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
- package/src/lib/components/console/site-member-purchases.component.js +339 -0
- package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
- package/src/lib/components/member-signup.js +2 -2
- package/src/lib/components/member-signup.js.map +1 -1
- package/src/lib/components/newsletter-signup.js +2 -2
- package/src/lib/components/newsletter-signup.js.map +1 -1
- package/src/lib/constants/operator-alerts.js +5 -3
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/model/commerce-io.js +1 -1
- package/src/lib/model/commerce-io.js.map +1 -1
- package/src/lib/model/commerce-record-lists.d.ts +31 -0
- package/src/lib/model/commerce-record-lists.js +45 -0
- package/src/lib/model/commerce-record-lists.js.map +1 -0
- package/src/lib/model/commerce-record-routes.d.ts +18 -0
- package/src/lib/model/commerce-record-routes.js +1 -1
- package/src/lib/model/commerce-record-routes.js.map +1 -1
- package/src/lib/model/commerce-search-sources.js +4 -2
- package/src/lib/model/commerce-search-sources.js.map +1 -1
- package/src/lib/model/commerce-topics.d.ts +20 -0
- package/src/lib/model/commerce-topics.js +33 -0
- package/src/lib/model/commerce-topics.js.map +1 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/product-record.d.ts +32 -0
- package/src/lib/model/product-record.js +82 -0
- package/src/lib/model/product-record.js.map +1 -0
- package/src/lib/model/site-member-purchases.d.ts +48 -0
- package/src/lib/model/site-member-purchases.js +50 -0
- package/src/lib/model/site-member-purchases.js.map +1 -0
- package/src/lib/plugin.js +12 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +410 -208
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +1 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/gift-cards.js +7 -3
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/low-stock.js +3 -1
- package/src/lib/server/low-stock.js.map +1 -1
- package/src/lib/server/member-post.js +2 -2
- package/src/lib/server/member-post.js.map +1 -1
- package/src/lib/server/membership-admin-password.js +4 -1
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-register.js +13 -2
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/newsletter.js +32 -19
- package/src/lib/server/newsletter.js.map +1 -1
- package/src/lib/server/pos-order.js +18 -10
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/process-abandoned.js +4 -2
- package/src/lib/server/process-abandoned.js.map +1 -1
- package/src/lib/server/process-restock.js +7 -5
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-record-index.js +5 -37
- package/src/lib/server/product-record-index.js.map +1 -1
- package/src/lib/server/reconcile-stock.js +1 -1
- package/src/lib/server/reconcile-stock.js.map +1 -1
- package/src/lib/server/reserve-stock.js +6 -1
- package/src/lib/server/reserve-stock.js.map +1 -1
- package/src/lib/server/revenue-source.js +3 -3
- package/src/lib/server/revenue-source.js.map +1 -1
- package/src/lib/server/supplier-outbox.js +16 -1
- package/src/lib/server/supplier-outbox.js.map +1 -1
- package/src/lib/server/supplier-update.js +19 -9
- package/src/lib/server/supplier-update.js.map +1 -1
- package/src/lib/starter-templates.d.ts +19 -0
- package/src/lib/starter-templates.js +157 -0
- package/src/lib/starter-templates.js.map +1 -0
- package/src/lib/tenant-emails.js +54 -14
- package/src/lib/tenant-emails.js.map +1 -1
- package/src/lib/testing/stand-in-record-system.d.ts +11 -2
- package/src/lib/testing/stand-in-record-system.js +5 -3
- package/src/lib/testing/stand-in-record-system.js.map +1 -1
|
@@ -24,7 +24,7 @@ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
|
24
24
|
import { Timestamp } from "@aglyn/shared-util-timestamp";
|
|
25
25
|
import { Alert, Autocomplete, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
26
26
|
import { collection, deleteDoc, doc, setDoc } from "firebase/firestore";
|
|
27
|
-
import { memo, useCallback, useMemo, useState } from "react";
|
|
27
|
+
import { memo, useCallback, useMemo, useRef, useState } from "react";
|
|
28
28
|
import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
|
|
29
29
|
import { ceilingedWindow, collectionCeiling, useFirestoreCollection, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
30
30
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
@@ -119,13 +119,571 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
119
119
|
}))
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
+
/**
|
|
123
|
+
* A text field that redraws only when what it shows changes (AGL-3423).
|
|
124
|
+
*
|
|
125
|
+
* Both editors keep their draft in this card, so a keystroke in either one
|
|
126
|
+
* re-renders the card, and drawn whole that is both lists with a match count
|
|
127
|
+
* per collection, their pagers, and every field of the dialog typed into.
|
|
128
|
+
* When a keystroke costs that much, typed input queues and is processed back
|
|
129
|
+
* to back, which is what lets a chip list's pending update climb to #185 (see
|
|
130
|
+
* `CollectionProductsField`). The lists and each part of the dialogs are
|
|
131
|
+
* memoized on what they show, and each field is this, handed a handler and
|
|
132
|
+
* styles that keep their identity, so the keystroke redraws the field it
|
|
133
|
+
* lands in.
|
|
134
|
+
*/ const MemoTextField = /*#__PURE__*/ memo(TextField);
|
|
135
|
+
const FIRST_FIELD = {
|
|
136
|
+
mt: 1
|
|
137
|
+
};
|
|
138
|
+
const RULE_FIELD = {
|
|
139
|
+
width: 130
|
|
140
|
+
};
|
|
141
|
+
const RULE_OP = {
|
|
142
|
+
width: 110
|
|
143
|
+
};
|
|
144
|
+
const RULE_VALUE = {
|
|
145
|
+
flex: 1
|
|
146
|
+
};
|
|
147
|
+
const MODE_MENU = [
|
|
148
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
149
|
+
value: "manual",
|
|
150
|
+
children: 'Manual — pick products'
|
|
151
|
+
}, "manual"),
|
|
152
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
153
|
+
value: "smart",
|
|
154
|
+
children: 'Smart — rule based'
|
|
155
|
+
}, "smart")
|
|
156
|
+
];
|
|
157
|
+
const RULE_FIELD_MENU = RULE_FIELDS.map((field)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
158
|
+
value: field.value,
|
|
159
|
+
children: field.label
|
|
160
|
+
}, field.value));
|
|
161
|
+
const RULE_OP_MENU = RULE_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
162
|
+
value: op.value,
|
|
163
|
+
children: op.label
|
|
164
|
+
}, op.value));
|
|
165
|
+
/** The category tree, a page at a time, and the button that adds one. */ const CategoriesSection = /*#__PURE__*/ memo(function CategoriesSection(props) {
|
|
166
|
+
const { categories, visible, onEdit, onDelete } = props;
|
|
167
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
168
|
+
children: [
|
|
169
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
170
|
+
variant: "subtitle2",
|
|
171
|
+
children: 'Categories'
|
|
172
|
+
}),
|
|
173
|
+
categories.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
174
|
+
variant: "body2",
|
|
175
|
+
color: "text.secondary",
|
|
176
|
+
children: 'Group products into a browsable tree (e.g. Brakes → Pads).'
|
|
177
|
+
}) : visible.map((category)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
178
|
+
direction: "row",
|
|
179
|
+
spacing: 1,
|
|
180
|
+
sx: {
|
|
181
|
+
alignItems: 'center',
|
|
182
|
+
pl: category.depth * 2
|
|
183
|
+
},
|
|
184
|
+
children: [
|
|
185
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
186
|
+
variant: "body2",
|
|
187
|
+
sx: {
|
|
188
|
+
flex: 1
|
|
189
|
+
},
|
|
190
|
+
noWrap: true,
|
|
191
|
+
children: [
|
|
192
|
+
category.name,
|
|
193
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
194
|
+
component: "span",
|
|
195
|
+
variant: "caption",
|
|
196
|
+
color: "text.secondary",
|
|
197
|
+
children: ` /${category.slug}`
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
}),
|
|
201
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
202
|
+
size: "small",
|
|
203
|
+
onClick: ()=>{
|
|
204
|
+
var _category_parentId;
|
|
205
|
+
return onEdit({
|
|
206
|
+
id: category.$id,
|
|
207
|
+
name: category.name,
|
|
208
|
+
parentId: (_category_parentId = category.parentId) != null ? _category_parentId : ''
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
children: 'Edit'
|
|
212
|
+
}),
|
|
213
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
214
|
+
size: "small",
|
|
215
|
+
color: "error",
|
|
216
|
+
onClick: onDelete(category),
|
|
217
|
+
children: 'Delete'
|
|
218
|
+
})
|
|
219
|
+
]
|
|
220
|
+
}, category.$id)),
|
|
221
|
+
categories.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
222
|
+
page: props.page,
|
|
223
|
+
pageSize: props.pageSize,
|
|
224
|
+
rowCount: visible.length,
|
|
225
|
+
// The categories the card HOLDS — a client slice of rows already
|
|
226
|
+
// read, so the total is known exactly.
|
|
227
|
+
count: categories.length,
|
|
228
|
+
onPageChange: props.onPageChange,
|
|
229
|
+
onPageSizeChange: props.onPageSizeChange
|
|
230
|
+
}),
|
|
231
|
+
props.truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
232
|
+
severity: "info",
|
|
233
|
+
children: `Showing ${CATEGORY_CEILING} categories, ordered by id. This ` + 'catalog has more — a category whose parent is past that ' + 'boundary is drawn at the top level here rather than under it.'
|
|
234
|
+
}) : null,
|
|
235
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
236
|
+
size: "small",
|
|
237
|
+
sx: {
|
|
238
|
+
alignSelf: 'flex-start'
|
|
239
|
+
},
|
|
240
|
+
onClick: ()=>onEdit({
|
|
241
|
+
id: null,
|
|
242
|
+
name: '',
|
|
243
|
+
parentId: ''
|
|
244
|
+
}),
|
|
245
|
+
children: 'Add category'
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
/** The catalog collections, a page at a time, and the button that adds one. */ const CollectionsSection = /*#__PURE__*/ memo(function CollectionsSection(props) {
|
|
251
|
+
const { collections, visible, products, countPrefix, onEdit, onDelete } = props;
|
|
252
|
+
/**
|
|
253
|
+
* How many products a collection holds — over the CATALOG WINDOW.
|
|
254
|
+
*
|
|
255
|
+
* Rendered with "at least" when the probe found a product past the ceiling,
|
|
256
|
+
* because past that point this is a lower bound and printing it as a total
|
|
257
|
+
* is the count-that-is-a-window-length defect. The number itself is honest
|
|
258
|
+
* either way; only the claim around it changes.
|
|
259
|
+
*/ const collectionCount = (row)=>products.filter((product)=>CommerceModel.matchesCollection(product, row, product.$id)).length;
|
|
260
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
261
|
+
children: [
|
|
262
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
263
|
+
variant: "subtitle2",
|
|
264
|
+
children: 'Collections'
|
|
265
|
+
}),
|
|
266
|
+
visible.map((row)=>{
|
|
267
|
+
var _row_mode;
|
|
268
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
269
|
+
direction: "row",
|
|
270
|
+
spacing: 1,
|
|
271
|
+
sx: {
|
|
272
|
+
alignItems: 'center'
|
|
273
|
+
},
|
|
274
|
+
children: [
|
|
275
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
276
|
+
variant: "body2",
|
|
277
|
+
sx: {
|
|
278
|
+
flex: 1
|
|
279
|
+
},
|
|
280
|
+
noWrap: true,
|
|
281
|
+
children: [
|
|
282
|
+
row.name,
|
|
283
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
284
|
+
component: "span",
|
|
285
|
+
variant: "caption",
|
|
286
|
+
color: "text.secondary",
|
|
287
|
+
children: ` · ${(_row_mode = row.mode) != null ? _row_mode : 'manual'} · ${countPrefix}${collectionCount(row)} products`
|
|
288
|
+
})
|
|
289
|
+
]
|
|
290
|
+
}),
|
|
291
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
292
|
+
size: "small",
|
|
293
|
+
onClick: ()=>{
|
|
294
|
+
var _row_slug, _row_mode, _row_productIds, _row_rules;
|
|
295
|
+
return onEdit({
|
|
296
|
+
id: row.$id,
|
|
297
|
+
name: row.name,
|
|
298
|
+
slug: (_row_slug = row.slug) != null ? _row_slug : '',
|
|
299
|
+
mode: (_row_mode = row.mode) != null ? _row_mode : 'manual',
|
|
300
|
+
productIds: (_row_productIds = row.productIds) != null ? _row_productIds : [],
|
|
301
|
+
rules: (_row_rules = row.rules) != null ? _row_rules : [],
|
|
302
|
+
matchAll: row.matchAll !== false
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
children: 'Edit'
|
|
306
|
+
}),
|
|
307
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
308
|
+
size: "small",
|
|
309
|
+
color: "error",
|
|
310
|
+
onClick: onDelete(row),
|
|
311
|
+
children: 'Delete'
|
|
312
|
+
})
|
|
313
|
+
]
|
|
314
|
+
}, row.$id);
|
|
315
|
+
}),
|
|
316
|
+
collections.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
317
|
+
page: props.page,
|
|
318
|
+
pageSize: props.pageSize,
|
|
319
|
+
rowCount: visible.length,
|
|
320
|
+
// The CATALOG collections, which is what this list is. Not the
|
|
321
|
+
// window's length: content collections share the subcollection
|
|
322
|
+
// and are classified out before this count is taken.
|
|
323
|
+
count: collections.length,
|
|
324
|
+
onPageChange: props.onPageChange,
|
|
325
|
+
onPageSizeChange: props.onPageSizeChange
|
|
326
|
+
}),
|
|
327
|
+
props.truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
328
|
+
severity: "info",
|
|
329
|
+
children: `Showing ${COLLECTION_CEILING} collections, ordered by id. This ` + 'site has more — the slug check below only covers the ones ' + 'listed here, so an address may already be taken by one that ' + 'is not.'
|
|
330
|
+
}) : null,
|
|
331
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
332
|
+
size: "small",
|
|
333
|
+
sx: {
|
|
334
|
+
alignSelf: 'flex-start'
|
|
335
|
+
},
|
|
336
|
+
onClick: ()=>onEdit({
|
|
337
|
+
id: null,
|
|
338
|
+
name: '',
|
|
339
|
+
slug: '',
|
|
340
|
+
mode: 'manual',
|
|
341
|
+
productIds: [],
|
|
342
|
+
rules: [],
|
|
343
|
+
matchAll: true
|
|
344
|
+
}),
|
|
345
|
+
children: 'Add collection'
|
|
346
|
+
})
|
|
347
|
+
]
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
/** The category editor: a name and a parent. */ const CategoryDialog = /*#__PURE__*/ memo(function CategoryDialog(props) {
|
|
351
|
+
var _ref, _ref1;
|
|
352
|
+
const { draft, categories, setDraft } = props;
|
|
353
|
+
const draftId = draft == null ? void 0 : draft.id;
|
|
354
|
+
const close = useCallback(()=>setDraft(null), [
|
|
355
|
+
setDraft
|
|
356
|
+
]);
|
|
357
|
+
const setName = useCallback((event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
358
|
+
name: event.target.value
|
|
359
|
+
}) : prev), [
|
|
360
|
+
setDraft
|
|
361
|
+
]);
|
|
362
|
+
const setParent = useCallback((event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
363
|
+
parentId: event.target.value
|
|
364
|
+
}) : prev), [
|
|
365
|
+
setDraft
|
|
366
|
+
]);
|
|
367
|
+
const parentMenu = useMemo(()=>[
|
|
368
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
369
|
+
value: "",
|
|
370
|
+
children: 'None (top level)'
|
|
371
|
+
}, ""),
|
|
372
|
+
// The whole set, not `visibleCategories` — a parent picker that
|
|
373
|
+
// offered only the current page could not reparent a category under
|
|
374
|
+
// one two pages away.
|
|
375
|
+
...categories.filter((category)=>category.$id !== draftId).map((category)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
376
|
+
value: category.$id,
|
|
377
|
+
children: `${'— '.repeat(category.depth)}${category.name}`
|
|
378
|
+
}, category.$id))
|
|
379
|
+
], [
|
|
380
|
+
categories,
|
|
381
|
+
draftId
|
|
382
|
+
]);
|
|
383
|
+
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
384
|
+
open: Boolean(draft),
|
|
385
|
+
onClose: close,
|
|
386
|
+
maxWidth: "xs",
|
|
387
|
+
fullWidth: true,
|
|
388
|
+
children: [
|
|
389
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
390
|
+
children: (draft == null ? void 0 : draft.id) ? 'Edit category' : 'New category'
|
|
391
|
+
}),
|
|
392
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
393
|
+
sx: {
|
|
394
|
+
display: 'flex',
|
|
395
|
+
flexDirection: 'column',
|
|
396
|
+
gap: 2
|
|
397
|
+
},
|
|
398
|
+
children: [
|
|
399
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
400
|
+
label: "Name",
|
|
401
|
+
value: (_ref = draft == null ? void 0 : draft.name) != null ? _ref : '',
|
|
402
|
+
onChange: setName,
|
|
403
|
+
size: "small",
|
|
404
|
+
autoFocus: true,
|
|
405
|
+
sx: FIRST_FIELD
|
|
406
|
+
}),
|
|
407
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
408
|
+
label: "Parent category",
|
|
409
|
+
value: (_ref1 = draft == null ? void 0 : draft.parentId) != null ? _ref1 : '',
|
|
410
|
+
onChange: setParent,
|
|
411
|
+
size: "small",
|
|
412
|
+
select: true,
|
|
413
|
+
children: parentMenu
|
|
414
|
+
})
|
|
415
|
+
]
|
|
416
|
+
}),
|
|
417
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
418
|
+
children: [
|
|
419
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
420
|
+
onClick: close,
|
|
421
|
+
children: 'Cancel'
|
|
422
|
+
}),
|
|
423
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
424
|
+
variant: "contained",
|
|
425
|
+
color: "primary",
|
|
426
|
+
disabled: !(draft == null ? void 0 : draft.name.trim()),
|
|
427
|
+
onClick: props.onSave,
|
|
428
|
+
children: 'Save'
|
|
429
|
+
})
|
|
430
|
+
]
|
|
431
|
+
})
|
|
432
|
+
]
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
const CollectionRuleRow = /*#__PURE__*/ memo(function CollectionRuleRow(props) {
|
|
436
|
+
const { index, rule, onChange } = props;
|
|
437
|
+
const setField = useCallback((event)=>onChange(index, {
|
|
438
|
+
field: event.target.value
|
|
439
|
+
}), [
|
|
440
|
+
index,
|
|
441
|
+
onChange
|
|
442
|
+
]);
|
|
443
|
+
const setOp = useCallback((event)=>onChange(index, {
|
|
444
|
+
op: event.target.value
|
|
445
|
+
}), [
|
|
446
|
+
index,
|
|
447
|
+
onChange
|
|
448
|
+
]);
|
|
449
|
+
const setCategory = useCallback((event)=>onChange(index, {
|
|
450
|
+
value: event.target.value
|
|
451
|
+
}), [
|
|
452
|
+
index,
|
|
453
|
+
onChange
|
|
454
|
+
]);
|
|
455
|
+
const ruleField = rule.field;
|
|
456
|
+
const setValue = useCallback((event)=>onChange(index, {
|
|
457
|
+
value: ruleField === 'priceUsd' ? Number(event.target.value) : event.target.value
|
|
458
|
+
}), [
|
|
459
|
+
index,
|
|
460
|
+
onChange,
|
|
461
|
+
ruleField
|
|
462
|
+
]);
|
|
463
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
464
|
+
direction: "row",
|
|
465
|
+
spacing: 1,
|
|
466
|
+
children: [
|
|
467
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
468
|
+
value: rule.field,
|
|
469
|
+
onChange: setField,
|
|
470
|
+
size: "small",
|
|
471
|
+
select: true,
|
|
472
|
+
sx: RULE_FIELD,
|
|
473
|
+
children: RULE_FIELD_MENU
|
|
474
|
+
}),
|
|
475
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
476
|
+
value: rule.op,
|
|
477
|
+
onChange: setOp,
|
|
478
|
+
size: "small",
|
|
479
|
+
select: true,
|
|
480
|
+
sx: RULE_OP,
|
|
481
|
+
children: RULE_OP_MENU
|
|
482
|
+
}),
|
|
483
|
+
rule.field === 'categoryId' ? /*#__PURE__*/ _jsx(MemoTextField, {
|
|
484
|
+
value: rule.value,
|
|
485
|
+
onChange: setCategory,
|
|
486
|
+
size: "small",
|
|
487
|
+
select: true,
|
|
488
|
+
sx: RULE_VALUE,
|
|
489
|
+
children: props.categoryMenu
|
|
490
|
+
}) : /*#__PURE__*/ _jsx(MemoTextField, {
|
|
491
|
+
value: rule.value,
|
|
492
|
+
onChange: setValue,
|
|
493
|
+
size: "small",
|
|
494
|
+
sx: RULE_VALUE,
|
|
495
|
+
placeholder: rule.field === 'type' ? 'physical' : 'Value'
|
|
496
|
+
}),
|
|
497
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
498
|
+
size: "small",
|
|
499
|
+
color: "error",
|
|
500
|
+
onClick: ()=>onChange(index, null),
|
|
501
|
+
children: '✕'
|
|
502
|
+
})
|
|
503
|
+
]
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
/** Whether every rule must hold, or any one. */ const CollectionMatchSwitch = /*#__PURE__*/ memo(function CollectionMatchSwitch(props) {
|
|
507
|
+
const { matchAll, setDraft } = props;
|
|
508
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
509
|
+
direction: "row",
|
|
510
|
+
spacing: 1,
|
|
511
|
+
sx: {
|
|
512
|
+
alignItems: 'center'
|
|
513
|
+
},
|
|
514
|
+
children: [
|
|
515
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
516
|
+
variant: "body2",
|
|
517
|
+
children: 'Match'
|
|
518
|
+
}),
|
|
519
|
+
/*#__PURE__*/ _jsx(Switch, {
|
|
520
|
+
size: "small",
|
|
521
|
+
checked: matchAll,
|
|
522
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
523
|
+
matchAll: event.target.checked
|
|
524
|
+
}) : prev)
|
|
525
|
+
}),
|
|
526
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
527
|
+
variant: "body2",
|
|
528
|
+
children: matchAll ? 'all rules' : 'any rule'
|
|
529
|
+
})
|
|
530
|
+
]
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
/** A smart collection's rules, whether all or any must hold, and the button that adds one. */ const CollectionRulesFields = /*#__PURE__*/ memo(function CollectionRulesFields(props) {
|
|
534
|
+
const { rules, matchAll, categories, setDraft, onRule } = props;
|
|
535
|
+
const categoryMenu = useMemo(()=>categories.map((category)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
536
|
+
value: category.$id,
|
|
537
|
+
children: category.name
|
|
538
|
+
}, category.$id)), [
|
|
539
|
+
categories
|
|
540
|
+
]);
|
|
541
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
542
|
+
children: [
|
|
543
|
+
/*#__PURE__*/ _jsx(CollectionMatchSwitch, {
|
|
544
|
+
matchAll: matchAll,
|
|
545
|
+
setDraft: setDraft
|
|
546
|
+
}),
|
|
547
|
+
rules.map((rule, index)=>/*#__PURE__*/ _jsx(CollectionRuleRow, {
|
|
548
|
+
index: index,
|
|
549
|
+
rule: rule,
|
|
550
|
+
categoryMenu: categoryMenu,
|
|
551
|
+
onChange: onRule
|
|
552
|
+
}, index)),
|
|
553
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
554
|
+
size: "small",
|
|
555
|
+
sx: {
|
|
556
|
+
alignSelf: 'flex-start'
|
|
557
|
+
},
|
|
558
|
+
onClick: ()=>onRule(rules.length, {}),
|
|
559
|
+
children: 'Add rule'
|
|
560
|
+
})
|
|
561
|
+
]
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
/** The products a draft collection holds, the first few by name. */ const CollectionPreview = /*#__PURE__*/ memo(function CollectionPreview(props) {
|
|
565
|
+
const { matches, countPrefix } = props;
|
|
566
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
567
|
+
direction: "row",
|
|
568
|
+
spacing: 0.5,
|
|
569
|
+
sx: {
|
|
570
|
+
flexWrap: 'wrap'
|
|
571
|
+
},
|
|
572
|
+
children: [
|
|
573
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
574
|
+
variant: "caption",
|
|
575
|
+
color: "text.secondary",
|
|
576
|
+
children: `Matches ${countPrefix}${matches.length} products: `
|
|
577
|
+
}),
|
|
578
|
+
matches.slice(0, 6).map((product)=>/*#__PURE__*/ _jsx(Chip, {
|
|
579
|
+
label: product.name,
|
|
580
|
+
size: "small"
|
|
581
|
+
}, product.$id)),
|
|
582
|
+
matches.length > 6 ? /*#__PURE__*/ _jsx(Typography, {
|
|
583
|
+
variant: "caption",
|
|
584
|
+
color: "text.secondary",
|
|
585
|
+
children: `+${matches.length - 6} more`
|
|
586
|
+
}) : null
|
|
587
|
+
]
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
/** The collection editor: a name, a mode, and the products or rules it holds. */ const CollectionDialog = /*#__PURE__*/ memo(function CollectionDialog(props) {
|
|
591
|
+
var _ref, _ref1, _ref2;
|
|
592
|
+
const { draft, error, setDraft } = props;
|
|
593
|
+
const close = useCallback(()=>setDraft(null), [
|
|
594
|
+
setDraft
|
|
595
|
+
]);
|
|
596
|
+
const setName = useCallback((event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
597
|
+
name: event.target.value
|
|
598
|
+
}) : prev), [
|
|
599
|
+
setDraft
|
|
600
|
+
]);
|
|
601
|
+
const setMode = useCallback((event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
602
|
+
mode: event.target.value
|
|
603
|
+
}) : prev), [
|
|
604
|
+
setDraft
|
|
605
|
+
]);
|
|
606
|
+
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
607
|
+
open: Boolean(draft),
|
|
608
|
+
onClose: close,
|
|
609
|
+
maxWidth: "sm",
|
|
610
|
+
fullWidth: true,
|
|
611
|
+
children: [
|
|
612
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
613
|
+
children: (draft == null ? void 0 : draft.id) ? 'Edit collection' : 'New collection'
|
|
614
|
+
}),
|
|
615
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
616
|
+
sx: {
|
|
617
|
+
display: 'flex',
|
|
618
|
+
flexDirection: 'column',
|
|
619
|
+
gap: 2
|
|
620
|
+
},
|
|
621
|
+
children: [
|
|
622
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
623
|
+
label: "Name",
|
|
624
|
+
value: (_ref = draft == null ? void 0 : draft.name) != null ? _ref : '',
|
|
625
|
+
onChange: setName,
|
|
626
|
+
size: "small",
|
|
627
|
+
autoFocus: true,
|
|
628
|
+
sx: FIRST_FIELD,
|
|
629
|
+
error: props.slugTaken,
|
|
630
|
+
helperText: (draft == null ? void 0 : draft.name) ? `/collections/${props.slug}` : undefined
|
|
631
|
+
}),
|
|
632
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
633
|
+
label: "Mode",
|
|
634
|
+
value: (_ref1 = draft == null ? void 0 : draft.mode) != null ? _ref1 : 'manual',
|
|
635
|
+
onChange: setMode,
|
|
636
|
+
size: "small",
|
|
637
|
+
select: true,
|
|
638
|
+
children: MODE_MENU
|
|
639
|
+
}),
|
|
640
|
+
(draft == null ? void 0 : draft.mode) === 'manual' ? /*#__PURE__*/ _jsx(CollectionProductsField, {
|
|
641
|
+
products: props.products,
|
|
642
|
+
value: props.picked,
|
|
643
|
+
onChange: props.onProducts
|
|
644
|
+
}) : /*#__PURE__*/ _jsx(CollectionRulesFields, {
|
|
645
|
+
rules: (_ref2 = draft == null ? void 0 : draft.rules) != null ? _ref2 : NO_RULES,
|
|
646
|
+
matchAll: (draft == null ? void 0 : draft.matchAll) !== false,
|
|
647
|
+
categories: props.categories,
|
|
648
|
+
setDraft: setDraft,
|
|
649
|
+
onRule: props.onRule
|
|
650
|
+
}),
|
|
651
|
+
error ? /*#__PURE__*/ _jsx(Alert, {
|
|
652
|
+
severity: "warning",
|
|
653
|
+
children: error
|
|
654
|
+
}) : null,
|
|
655
|
+
draft ? /*#__PURE__*/ _jsx(CollectionPreview, {
|
|
656
|
+
matches: props.matches,
|
|
657
|
+
countPrefix: props.countPrefix
|
|
658
|
+
}) : null
|
|
659
|
+
]
|
|
660
|
+
}),
|
|
661
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
662
|
+
children: [
|
|
663
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
664
|
+
onClick: close,
|
|
665
|
+
children: 'Cancel'
|
|
666
|
+
}),
|
|
667
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
668
|
+
variant: "contained",
|
|
669
|
+
color: "primary",
|
|
670
|
+
disabled: !(draft == null ? void 0 : draft.name.trim()) || Boolean(error),
|
|
671
|
+
onClick: props.onSave,
|
|
672
|
+
children: 'Save'
|
|
673
|
+
})
|
|
674
|
+
]
|
|
675
|
+
})
|
|
676
|
+
]
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
/** A collection with no rules yet, the same list on every render. */ const NO_RULES = [];
|
|
122
680
|
/**
|
|
123
681
|
* Categories & collections manager (AGL-280): category tree (parentId)
|
|
124
682
|
* at `hosts/{hostId}/productCategories`, manual + smart collections at
|
|
125
683
|
* `hosts/{hostId}/collections` with a live matched-product preview from
|
|
126
684
|
* the same `matchesCollection` matcher the storefront uses.
|
|
127
685
|
*/ export function CatalogOrganizationCard(props) {
|
|
128
|
-
var _CommerceModel_validateCollection
|
|
686
|
+
var _CommerceModel_validateCollection;
|
|
129
687
|
const { hostId } = props;
|
|
130
688
|
const firestore = useFirestore();
|
|
131
689
|
const { enqueueSnackbar } = useSnackbar();
|
|
@@ -459,14 +1017,27 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
459
1017
|
const setCollectionProducts = useCallback((picked)=>setCollectionDraft((prev)=>prev ? _extends({}, prev, {
|
|
460
1018
|
productIds: picked.map((item)=>item.$id)
|
|
461
1019
|
}) : prev), []);
|
|
1020
|
+
/*
|
|
1021
|
+
* Recomputed when what decides membership changes — the mode, the picked
|
|
1022
|
+
* products, the rules and whether all must hold — and not on a keystroke in
|
|
1023
|
+
* the name, which no match reads (AGL-3423).
|
|
1024
|
+
*/ const draftMode = collectionDraft == null ? void 0 : collectionDraft.mode;
|
|
1025
|
+
const draftRules = collectionDraft == null ? void 0 : collectionDraft.rules;
|
|
1026
|
+
const draftMatchAll = collectionDraft == null ? void 0 : collectionDraft.matchAll;
|
|
462
1027
|
const previewMatches = useMemo(()=>{
|
|
463
|
-
if (!
|
|
464
|
-
const candidate =
|
|
465
|
-
|
|
466
|
-
|
|
1028
|
+
if (!draftMode) return [];
|
|
1029
|
+
const candidate = {
|
|
1030
|
+
mode: draftMode,
|
|
1031
|
+
productIds: collectionProductIds,
|
|
1032
|
+
rules: draftRules,
|
|
1033
|
+
matchAll: draftMatchAll
|
|
1034
|
+
};
|
|
467
1035
|
return products.filter((product)=>CommerceModel.matchesCollection(product, candidate, product.$id));
|
|
468
1036
|
}, [
|
|
469
|
-
|
|
1037
|
+
draftMode,
|
|
1038
|
+
collectionProductIds,
|
|
1039
|
+
draftRules,
|
|
1040
|
+
draftMatchAll,
|
|
470
1041
|
products
|
|
471
1042
|
]);
|
|
472
1043
|
/**
|
|
@@ -660,14 +1231,6 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
660
1231
|
enqueueSnackbar,
|
|
661
1232
|
restampMembership
|
|
662
1233
|
]);
|
|
663
|
-
/**
|
|
664
|
-
* How many products a collection holds — over the CATALOG WINDOW.
|
|
665
|
-
*
|
|
666
|
-
* Rendered with "at least" when the probe found a product past the ceiling,
|
|
667
|
-
* because past that point this is a lower bound and printing it as a total
|
|
668
|
-
* is the count-that-is-a-window-length defect. The number itself is honest
|
|
669
|
-
* either way; only the claim around it changes.
|
|
670
|
-
*/ const collectionCount = (row)=>products.filter((product)=>CommerceModel.matchesCollection(product, row, product.$id)).length;
|
|
671
1234
|
/*
|
|
672
1235
|
* "at least", or nothing at all.
|
|
673
1236
|
*
|
|
@@ -680,11 +1243,18 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
680
1243
|
* because a catalog of exactly the ceiling is complete: nothing is missing,
|
|
681
1244
|
* and a comparison would put "at least" on a number that is exact.
|
|
682
1245
|
*/ const countPrefix = productsTruncated ? 'at least ' : '';
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1246
|
+
/*
|
|
1247
|
+
* A rule's change, written over the collection draft as last rendered, from
|
|
1248
|
+
* a handler that keeps its identity so a rule row redraws only when its own
|
|
1249
|
+
* rule changes (AGL-3423).
|
|
1250
|
+
*/ const latestCollectionDraft = useRef(collectionDraft);
|
|
1251
|
+
latestCollectionDraft.current = collectionDraft;
|
|
1252
|
+
const updateRule = useCallback((index, patch)=>{
|
|
1253
|
+
var _draft_rules;
|
|
1254
|
+
const draft = latestCollectionDraft.current;
|
|
1255
|
+
if (!draft) return;
|
|
686
1256
|
const rules = [
|
|
687
|
-
...(
|
|
1257
|
+
...(_draft_rules = draft.rules) != null ? _draft_rules : []
|
|
688
1258
|
];
|
|
689
1259
|
if (patch === null) rules.splice(index, 1);
|
|
690
1260
|
else rules[index] = _extends({
|
|
@@ -692,10 +1262,10 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
692
1262
|
op: 'eq',
|
|
693
1263
|
value: ''
|
|
694
1264
|
}, rules[index], patch);
|
|
695
|
-
setCollectionDraft(_extends({},
|
|
1265
|
+
setCollectionDraft(_extends({}, draft, {
|
|
696
1266
|
rules
|
|
697
1267
|
}));
|
|
698
|
-
};
|
|
1268
|
+
}, []);
|
|
699
1269
|
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
700
1270
|
header: 'Categories & collections',
|
|
701
1271
|
help: pluginDocsHelp('catalog', {
|
|
@@ -707,447 +1277,57 @@ const sameProduct = (option, value)=>option.$id === value.$id;
|
|
|
707
1277
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
708
1278
|
spacing: 1,
|
|
709
1279
|
children: [
|
|
710
|
-
/*#__PURE__*/ _jsx(
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}),
|
|
714
|
-
categories.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
715
|
-
variant: "body2",
|
|
716
|
-
color: "text.secondary",
|
|
717
|
-
children: 'Group products into a browsable tree (e.g. Brakes → Pads).'
|
|
718
|
-
}) : visibleCategories.map((category)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
719
|
-
direction: "row",
|
|
720
|
-
spacing: 1,
|
|
721
|
-
sx: {
|
|
722
|
-
alignItems: 'center',
|
|
723
|
-
pl: category.depth * 2
|
|
724
|
-
},
|
|
725
|
-
children: [
|
|
726
|
-
/*#__PURE__*/ _jsxs(Typography, {
|
|
727
|
-
variant: "body2",
|
|
728
|
-
sx: {
|
|
729
|
-
flex: 1
|
|
730
|
-
},
|
|
731
|
-
noWrap: true,
|
|
732
|
-
children: [
|
|
733
|
-
category.name,
|
|
734
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
735
|
-
component: "span",
|
|
736
|
-
variant: "caption",
|
|
737
|
-
color: "text.secondary",
|
|
738
|
-
children: ` /${category.slug}`
|
|
739
|
-
})
|
|
740
|
-
]
|
|
741
|
-
}),
|
|
742
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
743
|
-
size: "small",
|
|
744
|
-
onClick: ()=>{
|
|
745
|
-
var _category_parentId;
|
|
746
|
-
return setCategoryDraft({
|
|
747
|
-
id: category.$id,
|
|
748
|
-
name: category.name,
|
|
749
|
-
parentId: (_category_parentId = category.parentId) != null ? _category_parentId : ''
|
|
750
|
-
});
|
|
751
|
-
},
|
|
752
|
-
children: 'Edit'
|
|
753
|
-
}),
|
|
754
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
755
|
-
size: "small",
|
|
756
|
-
color: "error",
|
|
757
|
-
onClick: handleCategoryDelete(category),
|
|
758
|
-
children: 'Delete'
|
|
759
|
-
})
|
|
760
|
-
]
|
|
761
|
-
}, category.$id)),
|
|
762
|
-
categories.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
1280
|
+
/*#__PURE__*/ _jsx(CategoriesSection, {
|
|
1281
|
+
categories: categories,
|
|
1282
|
+
visible: visibleCategories,
|
|
763
1283
|
page: categoryPage,
|
|
764
1284
|
pageSize: categoryPageSize,
|
|
765
|
-
|
|
766
|
-
// The categories the card HOLDS — a client slice of rows already
|
|
767
|
-
// read, so the total is known exactly.
|
|
768
|
-
count: categories.length,
|
|
1285
|
+
truncated: categoriesTruncated,
|
|
769
1286
|
onPageChange: setCategoryPage,
|
|
770
|
-
onPageSizeChange: setCategoryPageSize
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
severity: "info",
|
|
774
|
-
children: `Showing ${CATEGORY_CEILING} categories, ordered by id. This ` + 'catalog has more — a category whose parent is past that ' + 'boundary is drawn at the top level here rather than under it.'
|
|
775
|
-
}) : null,
|
|
776
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
777
|
-
size: "small",
|
|
778
|
-
sx: {
|
|
779
|
-
alignSelf: 'flex-start'
|
|
780
|
-
},
|
|
781
|
-
onClick: ()=>setCategoryDraft({
|
|
782
|
-
id: null,
|
|
783
|
-
name: '',
|
|
784
|
-
parentId: ''
|
|
785
|
-
}),
|
|
786
|
-
children: 'Add category'
|
|
1287
|
+
onPageSizeChange: setCategoryPageSize,
|
|
1288
|
+
onEdit: setCategoryDraft,
|
|
1289
|
+
onDelete: handleCategoryDelete
|
|
787
1290
|
}),
|
|
788
1291
|
/*#__PURE__*/ _jsx(Divider, {
|
|
789
1292
|
sx: {
|
|
790
1293
|
my: 1
|
|
791
1294
|
}
|
|
792
1295
|
}),
|
|
793
|
-
/*#__PURE__*/ _jsx(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
var _row_mode;
|
|
799
|
-
return /*#__PURE__*/ _jsxs(Stack, {
|
|
800
|
-
direction: "row",
|
|
801
|
-
spacing: 1,
|
|
802
|
-
sx: {
|
|
803
|
-
alignItems: 'center'
|
|
804
|
-
},
|
|
805
|
-
children: [
|
|
806
|
-
/*#__PURE__*/ _jsxs(Typography, {
|
|
807
|
-
variant: "body2",
|
|
808
|
-
sx: {
|
|
809
|
-
flex: 1
|
|
810
|
-
},
|
|
811
|
-
noWrap: true,
|
|
812
|
-
children: [
|
|
813
|
-
row.name,
|
|
814
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
815
|
-
component: "span",
|
|
816
|
-
variant: "caption",
|
|
817
|
-
color: "text.secondary",
|
|
818
|
-
children: ` · ${(_row_mode = row.mode) != null ? _row_mode : 'manual'} · ${countPrefix}${collectionCount(row)} products`
|
|
819
|
-
})
|
|
820
|
-
]
|
|
821
|
-
}),
|
|
822
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
823
|
-
size: "small",
|
|
824
|
-
onClick: ()=>{
|
|
825
|
-
var _row_slug, _row_mode, _row_productIds, _row_rules;
|
|
826
|
-
return setCollectionDraft({
|
|
827
|
-
id: row.$id,
|
|
828
|
-
name: row.name,
|
|
829
|
-
slug: (_row_slug = row.slug) != null ? _row_slug : '',
|
|
830
|
-
mode: (_row_mode = row.mode) != null ? _row_mode : 'manual',
|
|
831
|
-
productIds: (_row_productIds = row.productIds) != null ? _row_productIds : [],
|
|
832
|
-
rules: (_row_rules = row.rules) != null ? _row_rules : [],
|
|
833
|
-
matchAll: row.matchAll !== false
|
|
834
|
-
});
|
|
835
|
-
},
|
|
836
|
-
children: 'Edit'
|
|
837
|
-
}),
|
|
838
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
839
|
-
size: "small",
|
|
840
|
-
color: "error",
|
|
841
|
-
onClick: handleCollectionDelete(row),
|
|
842
|
-
children: 'Delete'
|
|
843
|
-
})
|
|
844
|
-
]
|
|
845
|
-
}, row.$id);
|
|
846
|
-
}),
|
|
847
|
-
commerceCollections.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
1296
|
+
/*#__PURE__*/ _jsx(CollectionsSection, {
|
|
1297
|
+
collections: commerceCollections,
|
|
1298
|
+
visible: visibleCollections,
|
|
1299
|
+
products: products,
|
|
1300
|
+
countPrefix: countPrefix,
|
|
848
1301
|
page: collectionPage,
|
|
849
1302
|
pageSize: collectionPageSize,
|
|
850
|
-
|
|
851
|
-
// The CATALOG collections, which is what this list is. Not the
|
|
852
|
-
// window's length: content collections share the subcollection
|
|
853
|
-
// and are classified out before this count is taken.
|
|
854
|
-
count: commerceCollections.length,
|
|
1303
|
+
truncated: collectionsTruncated,
|
|
855
1304
|
onPageChange: setCollectionPage,
|
|
856
|
-
onPageSizeChange: setCollectionPageSize
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
severity: "info",
|
|
860
|
-
children: `Showing ${COLLECTION_CEILING} collections, ordered by id. This ` + 'site has more — the slug check below only covers the ones ' + 'listed here, so an address may already be taken by one that ' + 'is not.'
|
|
861
|
-
}) : null,
|
|
862
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
863
|
-
size: "small",
|
|
864
|
-
sx: {
|
|
865
|
-
alignSelf: 'flex-start'
|
|
866
|
-
},
|
|
867
|
-
onClick: ()=>setCollectionDraft({
|
|
868
|
-
id: null,
|
|
869
|
-
name: '',
|
|
870
|
-
slug: '',
|
|
871
|
-
mode: 'manual',
|
|
872
|
-
productIds: [],
|
|
873
|
-
rules: [],
|
|
874
|
-
matchAll: true
|
|
875
|
-
}),
|
|
876
|
-
children: 'Add collection'
|
|
1305
|
+
onPageSizeChange: setCollectionPageSize,
|
|
1306
|
+
onEdit: setCollectionDraft,
|
|
1307
|
+
onDelete: handleCollectionDelete
|
|
877
1308
|
})
|
|
878
1309
|
]
|
|
879
1310
|
}),
|
|
880
|
-
/*#__PURE__*/
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
children: [
|
|
886
|
-
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
887
|
-
children: (categoryDraft == null ? void 0 : categoryDraft.id) ? 'Edit category' : 'New category'
|
|
888
|
-
}),
|
|
889
|
-
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
890
|
-
sx: {
|
|
891
|
-
display: 'flex',
|
|
892
|
-
flexDirection: 'column',
|
|
893
|
-
gap: 2
|
|
894
|
-
},
|
|
895
|
-
children: [
|
|
896
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
897
|
-
label: "Name",
|
|
898
|
-
value: (_ref = categoryDraft == null ? void 0 : categoryDraft.name) != null ? _ref : '',
|
|
899
|
-
onChange: (event)=>setCategoryDraft((prev)=>prev ? _extends({}, prev, {
|
|
900
|
-
name: event.target.value
|
|
901
|
-
}) : prev),
|
|
902
|
-
size: "small",
|
|
903
|
-
autoFocus: true,
|
|
904
|
-
sx: {
|
|
905
|
-
mt: 1
|
|
906
|
-
}
|
|
907
|
-
}),
|
|
908
|
-
/*#__PURE__*/ _jsxs(TextField, {
|
|
909
|
-
label: "Parent category",
|
|
910
|
-
value: (_ref1 = categoryDraft == null ? void 0 : categoryDraft.parentId) != null ? _ref1 : '',
|
|
911
|
-
onChange: (event)=>setCategoryDraft((prev)=>prev ? _extends({}, prev, {
|
|
912
|
-
parentId: event.target.value
|
|
913
|
-
}) : prev),
|
|
914
|
-
size: "small",
|
|
915
|
-
select: true,
|
|
916
|
-
children: [
|
|
917
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
918
|
-
value: "",
|
|
919
|
-
children: 'None (top level)'
|
|
920
|
-
}),
|
|
921
|
-
categories.filter((category)=>category.$id !== (categoryDraft == null ? void 0 : categoryDraft.id)).map((category)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
922
|
-
value: category.$id,
|
|
923
|
-
children: `${'— '.repeat(category.depth)}${category.name}`
|
|
924
|
-
}, category.$id))
|
|
925
|
-
]
|
|
926
|
-
})
|
|
927
|
-
]
|
|
928
|
-
}),
|
|
929
|
-
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
930
|
-
children: [
|
|
931
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
932
|
-
onClick: ()=>setCategoryDraft(null),
|
|
933
|
-
children: 'Cancel'
|
|
934
|
-
}),
|
|
935
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
936
|
-
variant: "contained",
|
|
937
|
-
color: "primary",
|
|
938
|
-
disabled: !(categoryDraft == null ? void 0 : categoryDraft.name.trim()),
|
|
939
|
-
onClick: handleCategorySave,
|
|
940
|
-
children: 'Save'
|
|
941
|
-
})
|
|
942
|
-
]
|
|
943
|
-
})
|
|
944
|
-
]
|
|
1311
|
+
/*#__PURE__*/ _jsx(CategoryDialog, {
|
|
1312
|
+
draft: categoryDraft,
|
|
1313
|
+
categories: categories,
|
|
1314
|
+
setDraft: setCategoryDraft,
|
|
1315
|
+
onSave: handleCategorySave
|
|
945
1316
|
}),
|
|
946
|
-
/*#__PURE__*/
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
},
|
|
961
|
-
children: [
|
|
962
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
963
|
-
label: "Name",
|
|
964
|
-
value: (_ref2 = collectionDraft == null ? void 0 : collectionDraft.name) != null ? _ref2 : '',
|
|
965
|
-
onChange: (event)=>setCollectionDraft((prev)=>prev ? _extends({}, prev, {
|
|
966
|
-
name: event.target.value
|
|
967
|
-
}) : prev),
|
|
968
|
-
size: "small",
|
|
969
|
-
autoFocus: true,
|
|
970
|
-
sx: {
|
|
971
|
-
mt: 1
|
|
972
|
-
},
|
|
973
|
-
error: slugTaken,
|
|
974
|
-
helperText: (collectionDraft == null ? void 0 : collectionDraft.name) ? `/collections/${draftSlug}` : undefined
|
|
975
|
-
}),
|
|
976
|
-
/*#__PURE__*/ _jsxs(TextField, {
|
|
977
|
-
label: "Mode",
|
|
978
|
-
value: (_ref3 = collectionDraft == null ? void 0 : collectionDraft.mode) != null ? _ref3 : 'manual',
|
|
979
|
-
onChange: (event)=>setCollectionDraft((prev)=>prev ? _extends({}, prev, {
|
|
980
|
-
mode: event.target.value
|
|
981
|
-
}) : prev),
|
|
982
|
-
size: "small",
|
|
983
|
-
select: true,
|
|
984
|
-
children: [
|
|
985
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
986
|
-
value: "manual",
|
|
987
|
-
children: 'Manual — pick products'
|
|
988
|
-
}),
|
|
989
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
990
|
-
value: "smart",
|
|
991
|
-
children: 'Smart — rule based'
|
|
992
|
-
})
|
|
993
|
-
]
|
|
994
|
-
}),
|
|
995
|
-
(collectionDraft == null ? void 0 : collectionDraft.mode) === 'manual' ? /*#__PURE__*/ _jsx(CollectionProductsField, {
|
|
996
|
-
products: products,
|
|
997
|
-
value: collectionProducts,
|
|
998
|
-
onChange: setCollectionProducts
|
|
999
|
-
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
1000
|
-
children: [
|
|
1001
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
1002
|
-
direction: "row",
|
|
1003
|
-
spacing: 1,
|
|
1004
|
-
sx: {
|
|
1005
|
-
alignItems: 'center'
|
|
1006
|
-
},
|
|
1007
|
-
children: [
|
|
1008
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
1009
|
-
variant: "body2",
|
|
1010
|
-
children: 'Match'
|
|
1011
|
-
}),
|
|
1012
|
-
/*#__PURE__*/ _jsx(Switch, {
|
|
1013
|
-
size: "small",
|
|
1014
|
-
checked: (collectionDraft == null ? void 0 : collectionDraft.matchAll) !== false,
|
|
1015
|
-
onChange: (event)=>setCollectionDraft((prev)=>prev ? _extends({}, prev, {
|
|
1016
|
-
matchAll: event.target.checked
|
|
1017
|
-
}) : prev)
|
|
1018
|
-
}),
|
|
1019
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
1020
|
-
variant: "body2",
|
|
1021
|
-
children: (collectionDraft == null ? void 0 : collectionDraft.matchAll) !== false ? 'all rules' : 'any rule'
|
|
1022
|
-
})
|
|
1023
|
-
]
|
|
1024
|
-
}),
|
|
1025
|
-
((_ref4 = collectionDraft == null ? void 0 : collectionDraft.rules) != null ? _ref4 : []).map((rule, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
1026
|
-
direction: "row",
|
|
1027
|
-
spacing: 1,
|
|
1028
|
-
children: [
|
|
1029
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
1030
|
-
value: rule.field,
|
|
1031
|
-
onChange: (event)=>updateRule(index, {
|
|
1032
|
-
field: event.target.value
|
|
1033
|
-
}),
|
|
1034
|
-
size: "small",
|
|
1035
|
-
select: true,
|
|
1036
|
-
sx: {
|
|
1037
|
-
width: 130
|
|
1038
|
-
},
|
|
1039
|
-
children: RULE_FIELDS.map((field)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1040
|
-
value: field.value,
|
|
1041
|
-
children: field.label
|
|
1042
|
-
}, field.value))
|
|
1043
|
-
}),
|
|
1044
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
1045
|
-
value: rule.op,
|
|
1046
|
-
onChange: (event)=>updateRule(index, {
|
|
1047
|
-
op: event.target.value
|
|
1048
|
-
}),
|
|
1049
|
-
size: "small",
|
|
1050
|
-
select: true,
|
|
1051
|
-
sx: {
|
|
1052
|
-
width: 110
|
|
1053
|
-
},
|
|
1054
|
-
children: RULE_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1055
|
-
value: op.value,
|
|
1056
|
-
children: op.label
|
|
1057
|
-
}, op.value))
|
|
1058
|
-
}),
|
|
1059
|
-
rule.field === 'categoryId' ? /*#__PURE__*/ _jsx(TextField, {
|
|
1060
|
-
value: rule.value,
|
|
1061
|
-
onChange: (event)=>updateRule(index, {
|
|
1062
|
-
value: event.target.value
|
|
1063
|
-
}),
|
|
1064
|
-
size: "small",
|
|
1065
|
-
select: true,
|
|
1066
|
-
sx: {
|
|
1067
|
-
flex: 1
|
|
1068
|
-
},
|
|
1069
|
-
children: categories.map((category)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1070
|
-
value: category.$id,
|
|
1071
|
-
children: category.name
|
|
1072
|
-
}, category.$id))
|
|
1073
|
-
}) : /*#__PURE__*/ _jsx(TextField, {
|
|
1074
|
-
value: rule.value,
|
|
1075
|
-
onChange: (event)=>updateRule(index, {
|
|
1076
|
-
value: rule.field === 'priceUsd' ? Number(event.target.value) : event.target.value
|
|
1077
|
-
}),
|
|
1078
|
-
size: "small",
|
|
1079
|
-
sx: {
|
|
1080
|
-
flex: 1
|
|
1081
|
-
},
|
|
1082
|
-
placeholder: rule.field === 'type' ? 'physical' : 'Value'
|
|
1083
|
-
}),
|
|
1084
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1085
|
-
size: "small",
|
|
1086
|
-
color: "error",
|
|
1087
|
-
onClick: ()=>updateRule(index, null),
|
|
1088
|
-
children: '✕'
|
|
1089
|
-
})
|
|
1090
|
-
]
|
|
1091
|
-
}, index)),
|
|
1092
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1093
|
-
size: "small",
|
|
1094
|
-
sx: {
|
|
1095
|
-
alignSelf: 'flex-start'
|
|
1096
|
-
},
|
|
1097
|
-
onClick: ()=>{
|
|
1098
|
-
var _ref;
|
|
1099
|
-
var _collectionDraft_rules;
|
|
1100
|
-
return updateRule((_ref = collectionDraft == null ? void 0 : (_collectionDraft_rules = collectionDraft.rules) == null ? void 0 : _collectionDraft_rules.length) != null ? _ref : 0, {});
|
|
1101
|
-
},
|
|
1102
|
-
children: 'Add rule'
|
|
1103
|
-
})
|
|
1104
|
-
]
|
|
1105
|
-
}),
|
|
1106
|
-
collectionError ? /*#__PURE__*/ _jsx(Alert, {
|
|
1107
|
-
severity: "warning",
|
|
1108
|
-
children: collectionError
|
|
1109
|
-
}) : null,
|
|
1110
|
-
collectionDraft ? /*#__PURE__*/ _jsxs(Stack, {
|
|
1111
|
-
direction: "row",
|
|
1112
|
-
spacing: 0.5,
|
|
1113
|
-
sx: {
|
|
1114
|
-
flexWrap: 'wrap'
|
|
1115
|
-
},
|
|
1116
|
-
children: [
|
|
1117
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
1118
|
-
variant: "caption",
|
|
1119
|
-
color: "text.secondary",
|
|
1120
|
-
children: `Matches ${countPrefix}${previewMatches.length} products: `
|
|
1121
|
-
}),
|
|
1122
|
-
previewMatches.slice(0, 6).map((product)=>/*#__PURE__*/ _jsx(Chip, {
|
|
1123
|
-
label: product.name,
|
|
1124
|
-
size: "small"
|
|
1125
|
-
}, product.$id)),
|
|
1126
|
-
previewMatches.length > 6 ? /*#__PURE__*/ _jsx(Typography, {
|
|
1127
|
-
variant: "caption",
|
|
1128
|
-
color: "text.secondary",
|
|
1129
|
-
children: `+${previewMatches.length - 6} more`
|
|
1130
|
-
}) : null
|
|
1131
|
-
]
|
|
1132
|
-
}) : null
|
|
1133
|
-
]
|
|
1134
|
-
}),
|
|
1135
|
-
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1136
|
-
children: [
|
|
1137
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1138
|
-
onClick: ()=>setCollectionDraft(null),
|
|
1139
|
-
children: 'Cancel'
|
|
1140
|
-
}),
|
|
1141
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1142
|
-
variant: "contained",
|
|
1143
|
-
color: "primary",
|
|
1144
|
-
disabled: !(collectionDraft == null ? void 0 : collectionDraft.name.trim()) || Boolean(collectionError),
|
|
1145
|
-
onClick: handleCollectionSave,
|
|
1146
|
-
children: 'Save'
|
|
1147
|
-
})
|
|
1148
|
-
]
|
|
1149
|
-
})
|
|
1150
|
-
]
|
|
1317
|
+
/*#__PURE__*/ _jsx(CollectionDialog, {
|
|
1318
|
+
draft: collectionDraft,
|
|
1319
|
+
slug: draftSlug,
|
|
1320
|
+
slugTaken: slugTaken,
|
|
1321
|
+
error: collectionError,
|
|
1322
|
+
products: products,
|
|
1323
|
+
picked: collectionProducts,
|
|
1324
|
+
categories: categories,
|
|
1325
|
+
matches: previewMatches,
|
|
1326
|
+
countPrefix: countPrefix,
|
|
1327
|
+
setDraft: setCollectionDraft,
|
|
1328
|
+
onProducts: setCollectionProducts,
|
|
1329
|
+
onRule: updateRule,
|
|
1330
|
+
onSave: handleCollectionSave
|
|
1151
1331
|
})
|
|
1152
1332
|
]
|
|
1153
1333
|
});
|