@aglyn/plugins-commerce 1.0.0-beta.291 → 1.0.0-beta.292
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/cart-fulfillment.d.ts +3 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -1
- package/src/lib/components/cart.js +14 -4
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/invoice-send.component.js +1 -1
- package/src/lib/components/console/invoice-send.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +1 -1
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
- package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
- package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +31 -5
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
- package/src/lib/components/console/product-register-fields.component.js +8 -197
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +6 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
- package/src/lib/components/console/quote-pay-link.component.js +29 -3
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
- package/src/lib/components/console/quote-send.component.js +1 -4
- package/src/lib/components/console/quote-send.component.js.map +1 -1
- package/src/lib/components/product-add-ons.d.ts +28 -0
- package/src/lib/components/product-add-ons.js +136 -0
- package/src/lib/components/product-add-ons.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +16 -0
- package/src/lib/components/product-detail.js +112 -19
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +8 -2
- package/src/lib/components/product-grid.js +6 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +15 -3
- package/src/lib/model/commerce-cart.js +10 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-invoices.d.ts +7 -0
- package/src/lib/model/commerce-invoices.js +11 -0
- package/src/lib/model/commerce-invoices.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +6 -3
- package/src/lib/model/commerce-orders.js +3 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
- package/src/lib/model/commerce-pos-kiosk.js +3 -2
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
- package/src/lib/model/commerce-quote-email.d.ts +14 -0
- package/src/lib/model/commerce-quote-email.js +25 -9
- package/src/lib/model/commerce-quote-email.js.map +1 -1
- package/src/lib/model/commerce-quotes.js +1 -1
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/commerce.d.ts +18 -0
- package/src/lib/model/commerce.js +26 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/product-modifiers.d.ts +74 -4
- package/src/lib/model/product-modifiers.js +131 -10
- package/src/lib/model/product-modifiers.js.map +1 -1
- package/src/lib/server/billing-webhook.js +64 -43
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +14 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.d.ts +5 -0
- package/src/lib/server/cart.js +41 -4
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +2 -0
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +3 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +28 -3
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment.js +5 -1
- package/src/lib/server/local-fulfillment.js.map +1 -1
- package/src/lib/server/pos-offline-sync.js +1 -1
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-order.js +1 -1
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/product.d.ts +26 -0
- package/src/lib/server/product.js +18 -0
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.js +5 -2
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/quote-email.js.map +1 -1
- package/src/lib/utils/add-to-cart.d.ts +8 -0
- package/src/lib/utils/add-to-cart.js +10 -2
- package/src/lib/utils/add-to-cart.js.map +1 -1
- package/src/lib/utils/quote-email-content.d.ts +1 -0
- package/src/lib/utils/quote-email-content.js +7 -4
- package/src/lib/utils/quote-email-content.js.map +1 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
19
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
20
|
+
import { Box, Button, Collapse, FormControlLabel, IconButton, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
21
|
+
import { memo, useCallback, useId, useState } from "react";
|
|
22
|
+
import * as CommerceModel from "../../model/index.js";
|
|
23
|
+
function newId(prefix) {
|
|
24
|
+
return `${prefix}${Math.random().toString(36).slice(2, 10)}`;
|
|
25
|
+
}
|
|
26
|
+
/** Dollars as typed, to whole cents; blank is free. */ function centsFromDollars(value) {
|
|
27
|
+
const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100);
|
|
28
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
29
|
+
}
|
|
30
|
+
/** Cents as the price field shows them; blank for free. */ function dollarsFromCents(cents) {
|
|
31
|
+
return cents ? (cents / 100).toFixed(2) : '';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* What a choice's per-variant prices are called (AGL-4100): by the one
|
|
35
|
+
* option a product varies by ("Price by size"), else by variant.
|
|
36
|
+
*/ export function priceByLabel(options) {
|
|
37
|
+
const named = options.filter((option)=>{
|
|
38
|
+
var _option_name;
|
|
39
|
+
return (_option_name = option.name) == null ? void 0 : _option_name.trim();
|
|
40
|
+
});
|
|
41
|
+
return named.length === 1 ? `Price by ${named[0].name.trim().toLowerCase()}` : 'Price by variant';
|
|
42
|
+
}
|
|
43
|
+
/** How a variant is named in a choice's price list. */ export function variantPriceLabel(variant) {
|
|
44
|
+
var _variant_options;
|
|
45
|
+
const values = Object.values((_variant_options = variant.options) != null ? _variant_options : {}).filter(Boolean);
|
|
46
|
+
return values.length ? values.join(' / ') : 'Default';
|
|
47
|
+
}
|
|
48
|
+
/** A new group, as the console makes it from now on: shown on the website (AGL-4100). */ export function newAddOnGroup() {
|
|
49
|
+
return {
|
|
50
|
+
id: newId('g'),
|
|
51
|
+
name: '',
|
|
52
|
+
min: 0,
|
|
53
|
+
max: 1,
|
|
54
|
+
online: true,
|
|
55
|
+
options: [
|
|
56
|
+
{
|
|
57
|
+
id: newId('o'),
|
|
58
|
+
name: '',
|
|
59
|
+
priceCents: 0
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A choice's prices by variant with one variant's price set or cleared. A
|
|
66
|
+
* cleared price follows the choice's own; with none left the field is
|
|
67
|
+
* dropped, never stored as `undefined` (the save is a client-direct write,
|
|
68
|
+
* which refuses one).
|
|
69
|
+
*/ export function withVariantPrice(option, variantId, raw) {
|
|
70
|
+
const { priceCentsByVariant } = option, rest = _object_without_properties_loose(option, [
|
|
71
|
+
"priceCentsByVariant"
|
|
72
|
+
]);
|
|
73
|
+
const next = _extends({}, priceCentsByVariant != null ? priceCentsByVariant : {});
|
|
74
|
+
if (raw.trim() === '') delete next[variantId];
|
|
75
|
+
else next[variantId] = centsFromDollars(raw);
|
|
76
|
+
return Object.keys(next).length ? _extends({}, rest, {
|
|
77
|
+
priceCentsByVariant: next
|
|
78
|
+
}) : rest;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The product's add-ons (AGL-4100, from the register's modifiers of
|
|
82
|
+
* AGL-3607): choices added to the item that are not stocked variants — walls
|
|
83
|
+
* and printing on a canopy, a milk in a latte — each free or adding to the
|
|
84
|
+
* price, and able to cost more on one variant than another. A group with a
|
|
85
|
+
* minimum of 1 or more must be answered before the item is added. Each group
|
|
86
|
+
* says whether the website's product page offers it; the register always
|
|
87
|
+
* does.
|
|
88
|
+
*
|
|
89
|
+
* Offered on every store, with or without the point of sale, and saved with
|
|
90
|
+
* the product, so editing it takes exactly what editing the product takes.
|
|
91
|
+
* Memoized on its own props like every section of this editor (AGL-3423).
|
|
92
|
+
*/ export const ProductAddOnsFields = /*#__PURE__*/ memo(function ProductAddOnsFields(props) {
|
|
93
|
+
const { modifierGroups, options, variants, update } = props;
|
|
94
|
+
const helpId = useId();
|
|
95
|
+
const [pricedByVariant, setPricedByVariant] = useState({});
|
|
96
|
+
const setGroups = useCallback((groups)=>update({
|
|
97
|
+
modifierGroups: groups
|
|
98
|
+
}), [
|
|
99
|
+
update
|
|
100
|
+
]);
|
|
101
|
+
const editGroup = (index, patch)=>setGroups(modifierGroups.map((group, at)=>at === index ? _extends({}, group, patch) : group));
|
|
102
|
+
const replaceOption = (groupIndex, optionIndex, next)=>{
|
|
103
|
+
const group = modifierGroups[groupIndex];
|
|
104
|
+
editGroup(groupIndex, {
|
|
105
|
+
options: group.options.map((option, at)=>at === optionIndex ? next : option)
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const editOption = (groupIndex, optionIndex, patch)=>replaceOption(groupIndex, optionIndex, _extends({}, modifierGroups[groupIndex].options[optionIndex], patch));
|
|
109
|
+
const problem = CommerceModel.modifierGroupsProblem(modifierGroups);
|
|
110
|
+
const variesByVariant = variants.length > 1;
|
|
111
|
+
const byLabel = priceByLabel(options);
|
|
112
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
113
|
+
spacing: 2,
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
116
|
+
id: helpId,
|
|
117
|
+
variant: "body2",
|
|
118
|
+
color: "text.secondary",
|
|
119
|
+
children: 'Add-ons are choices added to the item that don’t need their own stock, such as walls or ' + 'printing on a canopy, or a milk in a latte. Each can be free or add to the price, and can ' + 'cost more on a larger size. Show a group on the website to offer it on the product’s page; ' + 'the register offers every group.'
|
|
120
|
+
}),
|
|
121
|
+
modifierGroups.map((group, groupIndex)=>/*#__PURE__*/ _jsx(Box, {
|
|
122
|
+
role: "group",
|
|
123
|
+
"aria-label": group.name || 'Add-on group',
|
|
124
|
+
sx: {
|
|
125
|
+
border: 1,
|
|
126
|
+
borderColor: 'divider',
|
|
127
|
+
borderRadius: 1,
|
|
128
|
+
p: 2
|
|
129
|
+
},
|
|
130
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
131
|
+
spacing: 1.5,
|
|
132
|
+
children: [
|
|
133
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
134
|
+
direction: "row",
|
|
135
|
+
spacing: 1,
|
|
136
|
+
sx: {
|
|
137
|
+
alignItems: 'flex-start',
|
|
138
|
+
flexWrap: 'wrap',
|
|
139
|
+
rowGap: 1
|
|
140
|
+
},
|
|
141
|
+
children: [
|
|
142
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
143
|
+
label: "Group name",
|
|
144
|
+
size: "small",
|
|
145
|
+
value: group.name,
|
|
146
|
+
onChange: (event)=>editGroup(groupIndex, {
|
|
147
|
+
name: event.target.value
|
|
148
|
+
}),
|
|
149
|
+
sx: {
|
|
150
|
+
flex: 1,
|
|
151
|
+
minWidth: (theme)=>theme.spacing(20)
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
155
|
+
label: "At least",
|
|
156
|
+
size: "small",
|
|
157
|
+
value: group.min,
|
|
158
|
+
onChange: (event)=>editGroup(groupIndex, {
|
|
159
|
+
min: Math.max(0, Math.round(Number(event.target.value) || 0))
|
|
160
|
+
}),
|
|
161
|
+
helperText: group.min > 0 ? 'Required' : 'Optional',
|
|
162
|
+
sx: {
|
|
163
|
+
width: (theme)=>theme.spacing(12)
|
|
164
|
+
},
|
|
165
|
+
slotProps: {
|
|
166
|
+
htmlInput: {
|
|
167
|
+
inputMode: 'numeric'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
172
|
+
label: "At most",
|
|
173
|
+
size: "small",
|
|
174
|
+
value: group.max,
|
|
175
|
+
onChange: (event)=>editGroup(groupIndex, {
|
|
176
|
+
max: Math.max(1, Math.round(Number(event.target.value) || 1))
|
|
177
|
+
}),
|
|
178
|
+
sx: {
|
|
179
|
+
width: (theme)=>theme.spacing(12)
|
|
180
|
+
},
|
|
181
|
+
slotProps: {
|
|
182
|
+
htmlInput: {
|
|
183
|
+
inputMode: 'numeric'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}),
|
|
187
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
188
|
+
color: "error",
|
|
189
|
+
onClick: ()=>setGroups(modifierGroups.filter((_group, at)=>at !== groupIndex)),
|
|
190
|
+
children: 'Remove group'
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
}),
|
|
194
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
195
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
196
|
+
checked: group.online === true,
|
|
197
|
+
onChange: (event)=>editGroup(groupIndex, {
|
|
198
|
+
online: event.target.checked
|
|
199
|
+
})
|
|
200
|
+
}),
|
|
201
|
+
label: "Show on the website"
|
|
202
|
+
}),
|
|
203
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
204
|
+
variant: "caption",
|
|
205
|
+
color: "text.secondary",
|
|
206
|
+
children: group.online === true ? 'Offered on the product’s page and at the register.' : 'Offered at the register only.'
|
|
207
|
+
}),
|
|
208
|
+
group.options.map((option, optionIndex)=>{
|
|
209
|
+
var _pricedByVariant_optionKey, _option_priceCentsByVariant;
|
|
210
|
+
const optionKey = `${group.id}:${option.id}`;
|
|
211
|
+
const byVariantOpen = (_pricedByVariant_optionKey = pricedByVariant[optionKey]) != null ? _pricedByVariant_optionKey : Object.keys((_option_priceCentsByVariant = option.priceCentsByVariant) != null ? _option_priceCentsByVariant : {}).length > 0;
|
|
212
|
+
return /*#__PURE__*/ _jsxs(Box, {
|
|
213
|
+
children: [
|
|
214
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
215
|
+
direction: "row",
|
|
216
|
+
spacing: 1,
|
|
217
|
+
sx: {
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
flexWrap: 'wrap',
|
|
220
|
+
rowGap: 1
|
|
221
|
+
},
|
|
222
|
+
children: [
|
|
223
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
224
|
+
label: "Choice",
|
|
225
|
+
size: "small",
|
|
226
|
+
value: option.name,
|
|
227
|
+
onChange: (event)=>editOption(groupIndex, optionIndex, {
|
|
228
|
+
name: event.target.value
|
|
229
|
+
}),
|
|
230
|
+
sx: {
|
|
231
|
+
flex: 1,
|
|
232
|
+
minWidth: (theme)=>theme.spacing(20)
|
|
233
|
+
}
|
|
234
|
+
}),
|
|
235
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
236
|
+
label: "Adds ($)",
|
|
237
|
+
size: "small",
|
|
238
|
+
defaultValue: dollarsFromCents(option.priceCents),
|
|
239
|
+
placeholder: "0.00",
|
|
240
|
+
onBlur: (event)=>editOption(groupIndex, optionIndex, {
|
|
241
|
+
priceCents: centsFromDollars(event.target.value)
|
|
242
|
+
}),
|
|
243
|
+
sx: {
|
|
244
|
+
width: (theme)=>theme.spacing(15)
|
|
245
|
+
},
|
|
246
|
+
slotProps: {
|
|
247
|
+
htmlInput: {
|
|
248
|
+
inputMode: 'decimal'
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}),
|
|
252
|
+
variesByVariant ? /*#__PURE__*/ _jsx(Button, {
|
|
253
|
+
size: "small",
|
|
254
|
+
"aria-expanded": byVariantOpen,
|
|
255
|
+
onClick: ()=>setPricedByVariant((open)=>_extends({}, open, {
|
|
256
|
+
[optionKey]: !byVariantOpen
|
|
257
|
+
})),
|
|
258
|
+
children: byLabel
|
|
259
|
+
}) : null,
|
|
260
|
+
/*#__PURE__*/ _jsx(IconButton, {
|
|
261
|
+
"aria-label": `Remove ${option.name || 'choice'}`,
|
|
262
|
+
onClick: ()=>editGroup(groupIndex, {
|
|
263
|
+
options: group.options.filter((_option, at)=>at !== optionIndex),
|
|
264
|
+
max: Math.max(1, Math.min(group.max, group.options.length - 1))
|
|
265
|
+
}),
|
|
266
|
+
children: '✕'
|
|
267
|
+
})
|
|
268
|
+
]
|
|
269
|
+
}),
|
|
270
|
+
variesByVariant ? /*#__PURE__*/ _jsx(Collapse, {
|
|
271
|
+
in: byVariantOpen,
|
|
272
|
+
unmountOnExit: true,
|
|
273
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
274
|
+
spacing: 1,
|
|
275
|
+
sx: {
|
|
276
|
+
pl: 2,
|
|
277
|
+
pt: 1.5
|
|
278
|
+
},
|
|
279
|
+
children: [
|
|
280
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
281
|
+
variant: "caption",
|
|
282
|
+
color: "text.secondary",
|
|
283
|
+
children: `What “${option.name || 'this choice'}” adds on each. Leave one blank to use ` + 'the price above.'
|
|
284
|
+
}),
|
|
285
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
286
|
+
sx: {
|
|
287
|
+
display: 'grid',
|
|
288
|
+
gridTemplateColumns: {
|
|
289
|
+
xs: '1fr',
|
|
290
|
+
sm: 'repeat(2, minmax(0, 1fr))'
|
|
291
|
+
},
|
|
292
|
+
gap: 1
|
|
293
|
+
},
|
|
294
|
+
children: variants.map((variant)=>{
|
|
295
|
+
var _option_priceCentsByVariant;
|
|
296
|
+
return /*#__PURE__*/ _jsx(TextField, {
|
|
297
|
+
label: variantPriceLabel(variant),
|
|
298
|
+
size: "small",
|
|
299
|
+
defaultValue: dollarsFromCents((_option_priceCentsByVariant = option.priceCentsByVariant) == null ? void 0 : _option_priceCentsByVariant[variant.id]),
|
|
300
|
+
placeholder: dollarsFromCents(option.priceCents) || '0.00',
|
|
301
|
+
onBlur: (event)=>replaceOption(groupIndex, optionIndex, withVariantPrice(modifierGroups[groupIndex].options[optionIndex], variant.id, event.target.value)),
|
|
302
|
+
slotProps: {
|
|
303
|
+
htmlInput: {
|
|
304
|
+
inputMode: 'decimal'
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}, variant.id);
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
]
|
|
311
|
+
})
|
|
312
|
+
}) : null
|
|
313
|
+
]
|
|
314
|
+
}, option.id);
|
|
315
|
+
}),
|
|
316
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
317
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
318
|
+
onClick: ()=>editGroup(groupIndex, {
|
|
319
|
+
options: [
|
|
320
|
+
...group.options,
|
|
321
|
+
{
|
|
322
|
+
id: newId('o'),
|
|
323
|
+
name: '',
|
|
324
|
+
priceCents: 0
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
}),
|
|
328
|
+
disabled: group.options.length >= CommerceModel.POS_MAX_MODIFIER_OPTIONS,
|
|
329
|
+
children: 'Add choice'
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
]
|
|
333
|
+
})
|
|
334
|
+
}, group.id)),
|
|
335
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
336
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
337
|
+
variant: "outlined",
|
|
338
|
+
"aria-describedby": helpId,
|
|
339
|
+
onClick: ()=>setGroups([
|
|
340
|
+
...modifierGroups,
|
|
341
|
+
newAddOnGroup()
|
|
342
|
+
]),
|
|
343
|
+
disabled: modifierGroups.length >= CommerceModel.POS_MAX_MODIFIER_GROUPS,
|
|
344
|
+
children: 'New add-on group'
|
|
345
|
+
})
|
|
346
|
+
}),
|
|
347
|
+
problem && modifierGroups.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
348
|
+
variant: "body2",
|
|
349
|
+
color: "warning.main",
|
|
350
|
+
children: problem
|
|
351
|
+
}) : null
|
|
352
|
+
]
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
//# sourceMappingURL=product-add-ons-fields.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/product-add-ons-fields.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n Box,\n Button,\n Collapse,\n FormControlLabel,\n IconButton,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback, useId, useState } from 'react'\nimport * as CommerceModel from '../../model'\n\ntype Patch = Partial<CommerceModel.HostProduct>\n\nexport interface ProductAddOnsFieldsProps {\n modifierGroups: readonly CommerceModel.ProductModifierGroup[]\n /** The product's options, to name what a choice is priced by. */\n options: readonly CommerceModel.ProductOption[]\n /** The product's variants, each of which a choice may be priced on. */\n variants: readonly Pick<CommerceModel.ProductVariant, 'id' | 'options'>[]\n update: (patch: Patch) => void\n}\n\nfunction newId(prefix: string): string {\n return `${prefix}${Math.random().toString(36).slice(2, 10)}`\n}\n\n/** Dollars as typed, to whole cents; blank is free. */\nfunction centsFromDollars(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/** Cents as the price field shows them; blank for free. */\nfunction dollarsFromCents(cents: number | undefined): string {\n return cents ? (cents / 100).toFixed(2) : ''\n}\n\n/**\n * What a choice's per-variant prices are called (AGL-4100): by the one\n * option a product varies by (\"Price by size\"), else by variant.\n */\nexport function priceByLabel(options: readonly Pick<CommerceModel.ProductOption, 'name'>[]): string {\n const named = options.filter((option) => option.name?.trim())\n return named.length === 1 ? `Price by ${named[0].name.trim().toLowerCase()}` : 'Price by variant'\n}\n\n/** How a variant is named in a choice's price list. */\nexport function variantPriceLabel(variant: Pick<CommerceModel.ProductVariant, 'options'>): string {\n const values = Object.values(variant.options ?? {}).filter(Boolean)\n return values.length ? values.join(' / ') : 'Default'\n}\n\n/** A new group, as the console makes it from now on: shown on the website (AGL-4100). */\nexport function newAddOnGroup(): CommerceModel.ProductModifierGroup {\n return {\n id: newId('g'),\n name: '',\n min: 0,\n max: 1,\n online: true,\n options: [{ id: newId('o'), name: '', priceCents: 0 }],\n }\n}\n\n/**\n * A choice's prices by variant with one variant's price set or cleared. A\n * cleared price follows the choice's own; with none left the field is\n * dropped, never stored as `undefined` (the save is a client-direct write,\n * which refuses one).\n */\nexport function withVariantPrice(\n option: CommerceModel.ProductModifierOption,\n variantId: string,\n raw: string,\n): CommerceModel.ProductModifierOption {\n const { priceCentsByVariant, ...rest } = option\n const next = { ...(priceCentsByVariant ?? {}) }\n if (raw.trim() === '') delete next[variantId]\n else next[variantId] = centsFromDollars(raw)\n return Object.keys(next).length ? { ...rest, priceCentsByVariant: next } : rest\n}\n\n/**\n * The product's add-ons (AGL-4100, from the register's modifiers of\n * AGL-3607): choices added to the item that are not stocked variants — walls\n * and printing on a canopy, a milk in a latte — each free or adding to the\n * price, and able to cost more on one variant than another. A group with a\n * minimum of 1 or more must be answered before the item is added. Each group\n * says whether the website's product page offers it; the register always\n * does.\n *\n * Offered on every store, with or without the point of sale, and saved with\n * the product, so editing it takes exactly what editing the product takes.\n * Memoized on its own props like every section of this editor (AGL-3423).\n */\nexport const ProductAddOnsFields = memo(function ProductAddOnsFields(\n props: ProductAddOnsFieldsProps,\n) {\n const { modifierGroups, options, variants, update } = props\n const helpId = useId()\n const [pricedByVariant, setPricedByVariant] = useState<Record<string, boolean>>({})\n const setGroups = useCallback(\n (groups: CommerceModel.ProductModifierGroup[]) => update({ modifierGroups: groups }),\n [update],\n )\n const editGroup = (index: number, patch: Partial<CommerceModel.ProductModifierGroup>) =>\n setGroups(modifierGroups.map((group, at) => (at === index ? { ...group, ...patch } : group)))\n const replaceOption = (\n groupIndex: number,\n optionIndex: number,\n next: CommerceModel.ProductModifierOption,\n ) => {\n const group = modifierGroups[groupIndex]\n editGroup(groupIndex, {\n options: group.options.map((option, at) => (at === optionIndex ? next : option)),\n })\n }\n const editOption = (\n groupIndex: number,\n optionIndex: number,\n patch: Partial<CommerceModel.ProductModifierOption>,\n ) => replaceOption(groupIndex, optionIndex, { ...modifierGroups[groupIndex].options[optionIndex], ...patch })\n const problem = CommerceModel.modifierGroupsProblem(modifierGroups)\n const variesByVariant = variants.length > 1\n const byLabel = priceByLabel(options)\n\n return (\n <Stack spacing={2}>\n <Typography id={helpId} variant=\"body2\" color=\"text.secondary\">\n {'Add-ons are choices added to the item that don’t need their own stock, such as walls or ' +\n 'printing on a canopy, or a milk in a latte. Each can be free or add to the price, and can ' +\n 'cost more on a larger size. Show a group on the website to offer it on the product’s page; ' +\n 'the register offers every group.'}\n </Typography>\n {modifierGroups.map((group, groupIndex) => (\n <Box\n key={group.id}\n role=\"group\"\n aria-label={group.name || 'Add-on group'}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 2 }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start', flexWrap: 'wrap', rowGap: 1 }}>\n <TextField\n label=\"Group name\"\n size=\"small\"\n value={group.name}\n onChange={(event) => editGroup(groupIndex, { name: event.target.value })}\n sx={{ flex: 1, minWidth: (theme) => theme.spacing(20) }}\n />\n <TextField\n label=\"At least\"\n size=\"small\"\n value={group.min}\n onChange={(event) =>\n editGroup(groupIndex, { min: Math.max(0, Math.round(Number(event.target.value) || 0)) })\n }\n helperText={group.min > 0 ? 'Required' : 'Optional'}\n sx={{ width: (theme) => theme.spacing(12) }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"At most\"\n size=\"small\"\n value={group.max}\n onChange={(event) =>\n editGroup(groupIndex, { max: Math.max(1, Math.round(Number(event.target.value) || 1)) })\n }\n sx={{ width: (theme) => theme.spacing(12) }}\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <Button\n color=\"error\"\n onClick={() => setGroups(modifierGroups.filter((_group, at) => at !== groupIndex))}\n >\n {'Remove group'}\n </Button>\n </Stack>\n <FormControlLabel\n control={\n <Switch\n checked={group.online === true}\n onChange={(event) => editGroup(groupIndex, { online: event.target.checked })}\n />\n }\n label=\"Show on the website\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {group.online === true\n ? 'Offered on the product’s page and at the register.'\n : 'Offered at the register only.'}\n </Typography>\n {group.options.map((option, optionIndex) => {\n const optionKey = `${group.id}:${option.id}`\n const byVariantOpen =\n pricedByVariant[optionKey] ?? Object.keys(option.priceCentsByVariant ?? {}).length > 0\n return (\n <Box key={option.id}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <TextField\n label=\"Choice\"\n size=\"small\"\n value={option.name}\n onChange={(event) => editOption(groupIndex, optionIndex, { name: event.target.value })}\n sx={{ flex: 1, minWidth: (theme) => theme.spacing(20) }}\n />\n <TextField\n label=\"Adds ($)\"\n size=\"small\"\n defaultValue={dollarsFromCents(option.priceCents)}\n placeholder=\"0.00\"\n onBlur={(event) =>\n editOption(groupIndex, optionIndex, { priceCents: centsFromDollars(event.target.value) })\n }\n sx={{ width: (theme) => theme.spacing(15) }}\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n {variesByVariant ? (\n <Button\n size=\"small\"\n aria-expanded={byVariantOpen}\n onClick={() =>\n setPricedByVariant((open) => ({ ...open, [optionKey]: !byVariantOpen }))\n }\n >\n {byLabel}\n </Button>\n ) : null}\n <IconButton\n aria-label={`Remove ${option.name || 'choice'}`}\n onClick={() =>\n editGroup(groupIndex, {\n options: group.options.filter((_option, at) => at !== optionIndex),\n max: Math.max(1, Math.min(group.max, group.options.length - 1)),\n })\n }\n >\n {'✕'}\n </IconButton>\n </Stack>\n {variesByVariant ? (\n <Collapse in={byVariantOpen} unmountOnExit>\n <Stack spacing={1} sx={{ pl: 2, pt: 1.5 }}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`What “${option.name || 'this choice'}” adds on each. Leave one blank to use ` +\n 'the price above.'}\n </Typography>\n <Box\n sx={{\n display: 'grid',\n gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))' },\n gap: 1,\n }}\n >\n {variants.map((variant) => (\n <TextField\n key={variant.id}\n label={variantPriceLabel(variant)}\n size=\"small\"\n defaultValue={dollarsFromCents(option.priceCentsByVariant?.[variant.id])}\n placeholder={dollarsFromCents(option.priceCents) || '0.00'}\n onBlur={(event) =>\n replaceOption(\n groupIndex,\n optionIndex,\n withVariantPrice(\n modifierGroups[groupIndex].options[optionIndex],\n variant.id,\n event.target.value,\n ),\n )\n }\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n ))}\n </Box>\n </Stack>\n </Collapse>\n ) : null}\n </Box>\n )\n })}\n <Box>\n <Button\n onClick={() =>\n editGroup(groupIndex, {\n options: [...group.options, { id: newId('o'), name: '', priceCents: 0 }],\n })\n }\n disabled={group.options.length >= CommerceModel.POS_MAX_MODIFIER_OPTIONS}\n >\n {'Add choice'}\n </Button>\n </Box>\n </Stack>\n </Box>\n ))}\n <Box>\n <Button\n variant=\"outlined\"\n aria-describedby={helpId}\n onClick={() => setGroups([...modifierGroups, newAddOnGroup()])}\n disabled={modifierGroups.length >= CommerceModel.POS_MAX_MODIFIER_GROUPS}\n >\n {'New add-on group'}\n </Button>\n </Box>\n {problem && modifierGroups.length ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {problem}\n </Typography>\n ) : null}\n </Stack>\n )\n})\n"],"names":["Box","Button","Collapse","FormControlLabel","IconButton","Stack","Switch","TextField","Typography","memo","useCallback","useId","useState","CommerceModel","newId","prefix","Math","random","toString","slice","centsFromDollars","value","number","round","Number","String","replace","isFinite","dollarsFromCents","cents","toFixed","priceByLabel","options","named","filter","option","name","trim","length","toLowerCase","variantPriceLabel","variant","values","Object","Boolean","join","newAddOnGroup","id","min","max","online","priceCents","withVariantPrice","variantId","raw","priceCentsByVariant","rest","next","keys","ProductAddOnsFields","props","modifierGroups","variants","update","helpId","pricedByVariant","setPricedByVariant","setGroups","groups","editGroup","index","patch","map","group","at","replaceOption","groupIndex","optionIndex","editOption","problem","modifierGroupsProblem","variesByVariant","byLabel","spacing","color","role","aria-label","sx","border","borderColor","borderRadius","p","direction","alignItems","flexWrap","rowGap","label","size","onChange","event","target","flex","minWidth","theme","helperText","width","slotProps","htmlInput","inputMode","onClick","_group","control","checked","optionKey","byVariantOpen","defaultValue","placeholder","onBlur","aria-expanded","open","_option","in","unmountOnExit","pl","pt","display","gridTemplateColumns","xs","sm","gap","disabled","POS_MAX_MODIFIER_OPTIONS","aria-describedby","POS_MAX_MODIFIER_GROUPS"],"mappings":"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"}
|
|
@@ -38,6 +38,7 @@ import { PRODUCT_EDITOR_ZONE } from "./product-zones.js";
|
|
|
38
38
|
import { MembersVideosField, PaidDownloadAddButton, PaidMediaProtection } from "./paid-media.js";
|
|
39
39
|
import { ProductCheckInFields } from "./product-check-in-fields.component.js";
|
|
40
40
|
import { ProductRegisterFields, ProductSellOnlineField } from "./product-register-fields.component.js";
|
|
41
|
+
import { ProductAddOnsFields } from "./product-add-ons-fields.component.js";
|
|
41
42
|
import { ProductKioskOfferField } from "./product-kiosk-offer-field.component.js";
|
|
42
43
|
/**
|
|
43
44
|
* The platform's own store's "Point of sale hardware" choice (AGL-4038),
|
|
@@ -1103,7 +1104,7 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1103
1104
|
* first variant so the legacy Product block + checkout API (AGL-90) keep
|
|
1104
1105
|
* charging correctly without reading the variants array.
|
|
1105
1106
|
*/ export function ProductEditorDialog(props) {
|
|
1106
|
-
var _ref, _ref1, _current_description, _current_tags, _current_categoryIds, _current_options, _current_mediaUrls, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _current_description1, _ref15, _ref16, _current_description2, _current_tags1, _current_mediaUrls1, _current_options1, _subs_planLabel, _gifts_planLabel, _subs_planLabel1, _gifts_planLabel1, _current_oversellPolicy, _current_modifierGroups, _current_digitalFiles, _current_gatedVideos;
|
|
1107
|
+
var _ref, _ref1, _current_description, _current_tags, _current_categoryIds, _current_options, _current_mediaUrls, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _current_description1, _ref15, _ref16, _current_description2, _current_tags1, _current_mediaUrls1, _current_options1, _subs_planLabel, _gifts_planLabel, _subs_planLabel1, _gifts_planLabel1, _current_oversellPolicy, _current_modifierGroups, _current_options2, _current_digitalFiles, _current_gatedVideos;
|
|
1107
1108
|
var _current_seo, _current_seo1, _current_shipping, _current_shipping1, _current_shipping2, _current_shipping3, _current_shipping4, _current_channel, _current_channel1, _current_channel2, _current_channel3, _current_channel4, _current_seo2, _current_seo3, _current_seo4, _current_seo5, _current_seo6, _current_seo7;
|
|
1108
1109
|
const { hostId, product, seedFromCache, seedUnreadable, open, onClose } = props;
|
|
1109
1110
|
/**
|
|
@@ -1355,7 +1356,12 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1355
1356
|
title: (_ref15 = (_current_seo2 = current.seo) == null ? void 0 : _current_seo2.title) != null ? _ref15 : '',
|
|
1356
1357
|
description: (_ref16 = (_current_seo3 = current.seo) == null ? void 0 : _current_seo3.description) != null ? _ref16 : ''
|
|
1357
1358
|
});
|
|
1358
|
-
|
|
1359
|
+
/**
|
|
1360
|
+
* More combinations than a product holds (AGL-4100): said with the count
|
|
1361
|
+
* beside the options, the values kept as typed, and Save refused — the
|
|
1362
|
+
* matrix used to stop at 100 and drop the rest without a word.
|
|
1363
|
+
*/ const matrixProblem = CommerceModel.variantMatrixProblem(current.options);
|
|
1364
|
+
const error = matrixProblem != null ? matrixProblem : current.name ? CommerceModel.validateProduct(current) : null;
|
|
1359
1365
|
/**
|
|
1360
1366
|
* Stock tracking is inert on a subscription-only product (AGL-1744) —
|
|
1361
1367
|
* nothing decrements it on the first charge or on any renewal, so the
|
|
@@ -1426,6 +1432,13 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1426
1432
|
name: '',
|
|
1427
1433
|
values: []
|
|
1428
1434
|
}, options[index], patch);
|
|
1435
|
+
// Past the variant ceiling (AGL-4100) the options are kept as typed
|
|
1436
|
+
// and the variants left as they are: nothing is built short, nothing
|
|
1437
|
+
// dropped, and Save stays refused (`matrixProblem`) until values are
|
|
1438
|
+
// removed — when the matrix is rebuilt from them as below.
|
|
1439
|
+
if (CommerceModel.variantMatrixProblem(options)) return {
|
|
1440
|
+
options
|
|
1441
|
+
};
|
|
1429
1442
|
// Regenerate the matrix, carrying data over by option-combo key so
|
|
1430
1443
|
// edits to prices/SKUs survive option tweaks.
|
|
1431
1444
|
const previous = new Map(at.variants.map((variant)=>[
|
|
@@ -1685,6 +1698,10 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1685
1698
|
onChange: handleOptionsChange,
|
|
1686
1699
|
onValues: setOptionValues
|
|
1687
1700
|
}),
|
|
1701
|
+
matrixProblem ? /*#__PURE__*/ _jsx(Alert, {
|
|
1702
|
+
severity: "warning",
|
|
1703
|
+
children: matrixProblem
|
|
1704
|
+
}) : null,
|
|
1688
1705
|
/*#__PURE__*/ _jsx(ProductVariantsTable, {
|
|
1689
1706
|
variants: current.variants,
|
|
1690
1707
|
stockApplies: stockApplies,
|
|
@@ -1731,13 +1748,22 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1731
1748
|
update: update,
|
|
1732
1749
|
replaceDraft: replaceDraft
|
|
1733
1750
|
}),
|
|
1751
|
+
/*#__PURE__*/ _jsx(Divider, {
|
|
1752
|
+
textAlign: "left",
|
|
1753
|
+
children: 'Add-ons'
|
|
1754
|
+
}),
|
|
1755
|
+
/*#__PURE__*/ _jsx(ProductAddOnsFields, {
|
|
1756
|
+
modifierGroups: (_current_modifierGroups = current.modifierGroups) != null ? _current_modifierGroups : NO_MODIFIER_GROUPS,
|
|
1757
|
+
options: (_current_options2 = current.options) != null ? _current_options2 : NO_OPTIONS,
|
|
1758
|
+
variants: current.variants,
|
|
1759
|
+
update: update
|
|
1760
|
+
}),
|
|
1734
1761
|
pos.ready && pos.entitled ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
1735
1762
|
children: [
|
|
1736
1763
|
/*#__PURE__*/ _jsx(ProductRegisterFields, {
|
|
1737
1764
|
posQuickKey: Boolean(current.posQuickKey),
|
|
1738
1765
|
sellOnline: CommerceModel.productSellsOnline(current),
|
|
1739
1766
|
inStoreReviews: current.inStoreReviews,
|
|
1740
|
-
modifierGroups: (_current_modifierGroups = current.modifierGroups) != null ? _current_modifierGroups : NO_MODIFIER_GROUPS,
|
|
1741
1767
|
update: update
|
|
1742
1768
|
}),
|
|
1743
1769
|
/*#__PURE__*/ _jsx(ProductKioskOfferField, {
|
|
@@ -1764,7 +1790,7 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1764
1790
|
/*#__PURE__*/ _jsx(EntitlementUpsell, {
|
|
1765
1791
|
planLabel: pos.planLabel,
|
|
1766
1792
|
upgradeHref: pos.upgradeHref,
|
|
1767
|
-
children: 'Register quick keys,
|
|
1793
|
+
children: 'Register quick keys, check-in at the window and selling a product in person only come with the point of sale.'
|
|
1768
1794
|
}),
|
|
1769
1795
|
!CommerceModel.productSellsOnline(current) ? /*#__PURE__*/ _jsx(ProductSellOnlineField, {
|
|
1770
1796
|
sellOnline: false,
|
|
@@ -1809,7 +1835,7 @@ const MemoPaidDownloadAddButton = /*#__PURE__*/ memo(PaidDownloadAddButton);
|
|
|
1809
1835
|
hasImage: Boolean((_current_seo7 = current.seo) == null ? void 0 : _current_seo7.imageUrl),
|
|
1810
1836
|
proposeValues: proposeSeoValues
|
|
1811
1837
|
}) : null,
|
|
1812
|
-
error ? /*#__PURE__*/ _jsx(Alert, {
|
|
1838
|
+
error && error !== matrixProblem ? /*#__PURE__*/ _jsx(Alert, {
|
|
1813
1839
|
severity: "warning",
|
|
1814
1840
|
children: error
|
|
1815
1841
|
}) : null,
|