@aglyn/plugins-commerce 1.0.0-beta.287 → 1.0.0-beta.289
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 +2 -2
- package/package.json +13 -13
- package/src/lib/components/commerce-console-page.js +29 -2
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +5 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
- package/src/lib/components/console/host-quotes-card.component.js +247 -0
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
- package/src/lib/components/console/order-webhooks-card.component.js +1 -1
- package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +1266 -0
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
- package/src/lib/components/console/quote-pay-link.component.js +213 -0
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
- package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
- package/src/lib/components/console/quote-settings-card.component.js +121 -0
- package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/quote-page-body.d.ts +33 -0
- package/src/lib/components/quote-page-body.js +696 -0
- package/src/lib/components/quote-page-body.js.map +1 -0
- package/src/lib/components/quote-page.d.ts +29 -0
- package/src/lib/components/quote-page.js +96 -0
- package/src/lib/components/quote-page.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +7 -1
- package/src/lib/components/storefront-payment-element.js +8 -3
- package/src/lib/components/storefront-payment-element.js.map +1 -1
- package/src/lib/constants/orders-list-query.js +6 -1
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/quote-page.d.ts +30 -0
- package/src/lib/constants/quote-page.js +35 -0
- package/src/lib/constants/quote-page.js.map +1 -0
- package/src/lib/declarations.js +5 -1
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +25 -2
- package/src/lib/model/commerce-orders.js +10 -1
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-quote-pay.d.ts +229 -0
- package/src/lib/model/commerce-quote-pay.js +393 -0
- package/src/lib/model/commerce-quote-pay.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +279 -0
- package/src/lib/model/commerce-quotes.js +522 -0
- package/src/lib/model/commerce-quotes.js.map +1 -0
- package/src/lib/model/index.d.ts +2 -0
- package/src/lib/model/index.js +2 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +8 -1
- package/src/lib/model/plugin-permissions.js +21 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/workspace-site-permissions.d.ts +26 -0
- package/src/lib/model/workspace-site-permissions.js +81 -0
- package/src/lib/model/workspace-site-permissions.js.map +1 -0
- package/src/lib/server/api-v1/orders-and-products.js +6 -2
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
- package/src/lib/server/billing-webhook.js +127 -52
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/checkout-session-expiry.d.ts +36 -0
- package/src/lib/server/checkout-session-expiry.js +63 -0
- package/src/lib/server/checkout-session-expiry.js.map +1 -0
- package/src/lib/server/commerce-site-keys.d.ts +19 -0
- package/src/lib/server/commerce-site-keys.js +23 -2
- package/src/lib/server/commerce-site-keys.js.map +1 -1
- package/src/lib/server/draft-order.js +16 -8
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/order-webhooks.js +8 -8
- package/src/lib/server/order-webhooks.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +7 -0
- package/src/lib/server/person-eraser.js +24 -2
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-ops-gate.d.ts +13 -5
- package/src/lib/server/pos-ops-gate.js +12 -14
- package/src/lib/server/pos-ops-gate.js.map +1 -1
- package/src/lib/server/pos-refund-authority.d.ts +3 -1
- package/src/lib/server/pos-refund-authority.js +3 -2
- package/src/lib/server/pos-refund-authority.js.map +1 -1
- package/src/lib/server/pos-return.js +6 -7
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-staff-pin.js +7 -7
- package/src/lib/server/pos-staff-pin.js.map +1 -1
- package/src/lib/server/quote-link-token.d.ts +39 -0
- package/src/lib/server/quote-link-token.js +97 -0
- package/src/lib/server/quote-link-token.js.map +1 -0
- package/src/lib/server/quote-pay.d.ts +83 -0
- package/src/lib/server/quote-pay.js +606 -0
- package/src/lib/server/quote-pay.js.map +1 -0
- package/src/lib/server/quote.d.ts +49 -0
- package/src/lib/server/quote.js +513 -0
- package/src/lib/server/quote.js.map +1 -0
- package/src/lib/server/refund.js +26 -36
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/related.js +2 -0
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/site-page-resolver.d.ts +2 -0
- package/src/lib/server/site-page-resolver.js +75 -0
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +6 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/transfer/tracking.server.js +6 -0
- package/src/lib/transfer/tracking.server.js.map +1 -1
|
@@ -0,0 +1,1266 @@
|
|
|
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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import * as CommerceModel from "../../model/index.js";
|
|
20
|
+
import { posMoney } from "../../model/commerce-pos-ops.js";
|
|
21
|
+
import { commerceSitePermissionRefusal } from "../../model/plugin-permissions.js";
|
|
22
|
+
import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
23
|
+
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
24
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
25
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
26
|
+
import { useFirestore, useFirestoreDoc, useStoreSettingsHref, useUser } from "@aglyn/tenant-feature-instance";
|
|
27
|
+
import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, FormControlLabel, List, ListItemButton, ListItemText, MenuItem, Paper, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
28
|
+
import { doc } from "firebase/firestore";
|
|
29
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
30
|
+
import { QuotePayLinkSection } from "./quote-pay-link.component.js";
|
|
31
|
+
const REFUSAL = commerceSitePermissionRefusal('commerce.quotes');
|
|
32
|
+
const SEARCH_DEBOUNCE_MS = 250;
|
|
33
|
+
const money = (cents)=>posMoney(cents);
|
|
34
|
+
/** Field widths on a wide screen, from the theme's spacing; full width on a phone. */ const narrowField = (theme)=>({
|
|
35
|
+
maxWidth: {
|
|
36
|
+
sm: theme.spacing(16)
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const mediumField = (theme)=>({
|
|
40
|
+
minWidth: {
|
|
41
|
+
sm: theme.spacing(20)
|
|
42
|
+
},
|
|
43
|
+
maxWidth: {
|
|
44
|
+
sm: theme.spacing(24)
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
/** Dollars typed into a field, as whole cents, or `null` when it is not an amount. */ function centsOf(text) {
|
|
48
|
+
const trimmed = text.trim();
|
|
49
|
+
if (!trimmed) return 0;
|
|
50
|
+
if (!/^\d+(\.\d{0,2})?$/.test(trimmed)) return null;
|
|
51
|
+
return Math.round(Number(trimmed) * 100);
|
|
52
|
+
}
|
|
53
|
+
const dollarsOf = (cents)=>cents ? (cents / 100).toFixed(2).replace(/\.00$/, '') : '';
|
|
54
|
+
function addressForm(address) {
|
|
55
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
|
|
56
|
+
return {
|
|
57
|
+
name: (_ref = address == null ? void 0 : address.name) != null ? _ref : '',
|
|
58
|
+
line1: (_ref1 = address == null ? void 0 : address.line1) != null ? _ref1 : '',
|
|
59
|
+
line2: (_ref2 = address == null ? void 0 : address.line2) != null ? _ref2 : '',
|
|
60
|
+
city: (_ref3 = address == null ? void 0 : address.city) != null ? _ref3 : '',
|
|
61
|
+
state: (_ref4 = address == null ? void 0 : address.state) != null ? _ref4 : '',
|
|
62
|
+
postalCode: (_ref5 = address == null ? void 0 : address.postalCode) != null ? _ref5 : '',
|
|
63
|
+
country: (_ref6 = address == null ? void 0 : address.country) != null ? _ref6 : ''
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let lineSequence = 0;
|
|
67
|
+
const nextLineKey = ()=>`line-${++lineSequence}`;
|
|
68
|
+
function formFromQuote(quote, requireProofApproval) {
|
|
69
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
|
|
70
|
+
var _section_customer, _section_customer1, _section_customer2, _section_customer3, _section_customer4, _section_shipping;
|
|
71
|
+
const section = quote == null ? void 0 : quote.quote;
|
|
72
|
+
const discount = (_ref = section == null ? void 0 : section.discount) != null ? _ref : null;
|
|
73
|
+
return {
|
|
74
|
+
lines: ((_ref1 = quote == null ? void 0 : quote.lineItems) != null ? _ref1 : []).map((line)=>{
|
|
75
|
+
var _request_description, _request_sku, _request_variantId, _request_modifiers, _request_description1;
|
|
76
|
+
const request = CommerceModel.quoteLineRequestFromLine(line);
|
|
77
|
+
return request.kind === 'custom' ? {
|
|
78
|
+
key: nextLineKey(),
|
|
79
|
+
kind: 'custom',
|
|
80
|
+
name: request.name,
|
|
81
|
+
description: (_request_description = request.description) != null ? _request_description : '',
|
|
82
|
+
sku: (_request_sku = request.sku) != null ? _request_sku : '',
|
|
83
|
+
price: dollarsOf(request.unitAmountCents),
|
|
84
|
+
quantity: String(request.quantity),
|
|
85
|
+
ships: request.ships !== false,
|
|
86
|
+
included: request.included === true
|
|
87
|
+
} : {
|
|
88
|
+
key: nextLineKey(),
|
|
89
|
+
kind: 'catalog',
|
|
90
|
+
productId: request.productId,
|
|
91
|
+
variantId: (_request_variantId = request.variantId) != null ? _request_variantId : '',
|
|
92
|
+
quantity: String(request.quantity),
|
|
93
|
+
modifiers: (_request_modifiers = request.modifiers) != null ? _request_modifiers : [],
|
|
94
|
+
description: (_request_description1 = request.description) != null ? _request_description1 : '',
|
|
95
|
+
included: request.included === true
|
|
96
|
+
};
|
|
97
|
+
}),
|
|
98
|
+
customer: {
|
|
99
|
+
record: (_ref2 = section == null ? void 0 : (_section_customer = section.customer) == null ? void 0 : _section_customer.record) != null ? _ref2 : null,
|
|
100
|
+
name: (_ref3 = section == null ? void 0 : (_section_customer1 = section.customer) == null ? void 0 : _section_customer1.name) != null ? _ref3 : '',
|
|
101
|
+
company: (_ref4 = section == null ? void 0 : (_section_customer2 = section.customer) == null ? void 0 : _section_customer2.company) != null ? _ref4 : '',
|
|
102
|
+
email: (_ref5 = section == null ? void 0 : (_section_customer3 = section.customer) == null ? void 0 : _section_customer3.email) != null ? _ref5 : '',
|
|
103
|
+
phone: (_ref6 = section == null ? void 0 : (_section_customer4 = section.customer) == null ? void 0 : _section_customer4.phone) != null ? _ref6 : ''
|
|
104
|
+
},
|
|
105
|
+
shipTo: addressForm(section == null ? void 0 : section.shipTo),
|
|
106
|
+
billTo: addressForm(section == null ? void 0 : section.billTo),
|
|
107
|
+
discountKind: (_ref7 = discount == null ? void 0 : discount.kind) != null ? _ref7 : 'none',
|
|
108
|
+
discountValue: (discount == null ? void 0 : discount.kind) === 'amount' ? dollarsOf(discount.amountCents) : (discount == null ? void 0 : discount.kind) === 'percent' ? String(discount.pct) : '',
|
|
109
|
+
shipping: dollarsOf(section == null ? void 0 : (_section_shipping = section.shipping) == null ? void 0 : _section_shipping.amountCents),
|
|
110
|
+
notes: (_ref8 = section == null ? void 0 : section.notes) != null ? _ref8 : '',
|
|
111
|
+
terms: (_ref9 = section == null ? void 0 : section.terms) != null ? _ref9 : '',
|
|
112
|
+
requireProofApproval: section ? section.requireProofApproval === true : requireProofApproval
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function lineRequest(line) {
|
|
116
|
+
var _centsOf;
|
|
117
|
+
const quantity = Number(line.quantity);
|
|
118
|
+
if (line.kind === 'catalog') {
|
|
119
|
+
return _extends({
|
|
120
|
+
kind: 'catalog',
|
|
121
|
+
productId: line.productId
|
|
122
|
+
}, line.variantId ? {
|
|
123
|
+
variantId: line.variantId
|
|
124
|
+
} : {}, {
|
|
125
|
+
quantity
|
|
126
|
+
}, line.modifiers.length ? {
|
|
127
|
+
modifiers: line.modifiers
|
|
128
|
+
} : {}, line.included ? {
|
|
129
|
+
included: true
|
|
130
|
+
} : {}, line.description.trim() ? {
|
|
131
|
+
description: line.description.trim()
|
|
132
|
+
} : {});
|
|
133
|
+
}
|
|
134
|
+
return _extends({
|
|
135
|
+
kind: 'custom',
|
|
136
|
+
name: line.name.trim()
|
|
137
|
+
}, line.description.trim() ? {
|
|
138
|
+
description: line.description.trim()
|
|
139
|
+
} : {}, line.sku.trim() ? {
|
|
140
|
+
sku: line.sku.trim()
|
|
141
|
+
} : {}, {
|
|
142
|
+
unitAmountCents: line.included ? 0 : (_centsOf = centsOf(line.price)) != null ? _centsOf : Number.NaN,
|
|
143
|
+
quantity,
|
|
144
|
+
ships: line.ships
|
|
145
|
+
}, line.included ? {
|
|
146
|
+
included: true
|
|
147
|
+
} : {});
|
|
148
|
+
}
|
|
149
|
+
function addressBody(address) {
|
|
150
|
+
const entries = Object.entries(address).filter(([, value])=>value.trim());
|
|
151
|
+
return entries.length ? Object.fromEntries(entries.map(([key, value])=>[
|
|
152
|
+
key,
|
|
153
|
+
value.trim()
|
|
154
|
+
])) : null;
|
|
155
|
+
}
|
|
156
|
+
/** What the server is sent: choices, never what a product costs. */ function quoteBody(form) {
|
|
157
|
+
var _centsOf;
|
|
158
|
+
const discountCents = centsOf(form.discountValue);
|
|
159
|
+
return {
|
|
160
|
+
lines: form.lines.map(lineRequest),
|
|
161
|
+
customer: _extends({}, form.customer.record ? {
|
|
162
|
+
record: form.customer.record
|
|
163
|
+
} : {}, {
|
|
164
|
+
name: form.customer.name,
|
|
165
|
+
company: form.customer.company,
|
|
166
|
+
email: form.customer.email,
|
|
167
|
+
phone: form.customer.phone
|
|
168
|
+
}),
|
|
169
|
+
shipTo: addressBody(form.shipTo),
|
|
170
|
+
billTo: addressBody(form.billTo),
|
|
171
|
+
discount: form.discountKind === 'amount' ? {
|
|
172
|
+
kind: 'amount',
|
|
173
|
+
amountCents: discountCents != null ? discountCents : Number.NaN
|
|
174
|
+
} : form.discountKind === 'percent' ? {
|
|
175
|
+
kind: 'percent',
|
|
176
|
+
pct: Number(form.discountValue || 0)
|
|
177
|
+
} : null,
|
|
178
|
+
shipping: form.shipping.trim() ? {
|
|
179
|
+
amountCents: (_centsOf = centsOf(form.shipping)) != null ? _centsOf : Number.NaN
|
|
180
|
+
} : null,
|
|
181
|
+
notes: form.notes,
|
|
182
|
+
terms: form.terms,
|
|
183
|
+
requireProofApproval: form.requireProofApproval
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The quote editor (AGL-4093): who it is for, its lines (products with their
|
|
188
|
+
* options and add-ons, typed items, things included at no charge), discount,
|
|
189
|
+
* shipping, notes and terms, with the totals worked out as it is edited. The
|
|
190
|
+
* server prices and saves it; the totals here are the same function's answer
|
|
191
|
+
* over the same inputs, and a connected tax service's figure arrives on save.
|
|
192
|
+
*/ export function QuoteEditorDialog(props) {
|
|
193
|
+
var _centsOf, _ref, _ref1;
|
|
194
|
+
var _quote_quote_tax, _quote_quote, _quote_quote1, _quote_quote2;
|
|
195
|
+
const { hostId, quote, products, canEdit, canSend, onClose } = props;
|
|
196
|
+
const firestore = useFirestore();
|
|
197
|
+
const { data: user } = useUser();
|
|
198
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
199
|
+
const taxesHref = useStoreSettingsHref(hostId, 'taxes');
|
|
200
|
+
const { data: store } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'settings', 'store'), [
|
|
201
|
+
firestore,
|
|
202
|
+
hostId
|
|
203
|
+
]);
|
|
204
|
+
const storeDefault = CommerceModel.quoteRequiresProofApprovalByDefault(store == null ? void 0 : store.quotes);
|
|
205
|
+
const [form, setForm] = useState(()=>formFromQuote(quote, storeDefault));
|
|
206
|
+
// The version this form was opened on. A change saved elsewhere while it is
|
|
207
|
+
// open arrives on `quote`; the save still names this one, so the server
|
|
208
|
+
// refuses rather than overwrite that change.
|
|
209
|
+
const [baseUpdatedAtMs] = useState(()=>{
|
|
210
|
+
var _quote_quote;
|
|
211
|
+
return quote == null ? void 0 : (_quote_quote = quote.quote) == null ? void 0 : _quote_quote.updatedAtMs;
|
|
212
|
+
});
|
|
213
|
+
const [busy, setBusy] = useState(false);
|
|
214
|
+
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
215
|
+
/** Whether anything has been changed since the dialog opened. */ const [edited, setEdited] = useState(false);
|
|
216
|
+
/** Whether the proof-approval switch was set by hand. */ const [approvalSet, setApprovalSet] = useState(false);
|
|
217
|
+
// A new quote starts with the store's default, which may arrive after the
|
|
218
|
+
// dialog opens; a switch set by hand keeps its setting.
|
|
219
|
+
const requireProofApproval = quote || approvalSet ? form.requireProofApproval : storeDefault;
|
|
220
|
+
const update = useCallback((patch)=>{
|
|
221
|
+
setEdited(true);
|
|
222
|
+
if ('requireProofApproval' in patch) setApprovalSet(true);
|
|
223
|
+
setForm((prev)=>_extends({}, prev, patch));
|
|
224
|
+
}, []);
|
|
225
|
+
const updateLine = useCallback((key, patch)=>{
|
|
226
|
+
setEdited(true);
|
|
227
|
+
setForm((prev)=>_extends({}, prev, {
|
|
228
|
+
lines: prev.lines.map((line)=>line.key === key ? _extends({}, line, patch) : line)
|
|
229
|
+
}));
|
|
230
|
+
}, []);
|
|
231
|
+
/*
|
|
232
|
+
* One attempt per change (AGL-1697's rule): a retried Save presents the same
|
|
233
|
+
* key and is answered from the first, an edited quote is a new attempt.
|
|
234
|
+
*/ const attemptKey = useRef('');
|
|
235
|
+
useEffect(()=>{
|
|
236
|
+
attemptKey.current = '';
|
|
237
|
+
}, [
|
|
238
|
+
form
|
|
239
|
+
]);
|
|
240
|
+
const productsById = useMemo(()=>new Map(products.map((product)=>[
|
|
241
|
+
String(product.$id),
|
|
242
|
+
product
|
|
243
|
+
])), [
|
|
244
|
+
products
|
|
245
|
+
]);
|
|
246
|
+
/** Each line priced as the server will price it, or why it cannot be. */ const priced = useMemo(()=>form.lines.map((line)=>{
|
|
247
|
+
const request = lineRequest(line);
|
|
248
|
+
if (request.kind === 'custom') {
|
|
249
|
+
if (!request.name) return {
|
|
250
|
+
error: 'Name the item.'
|
|
251
|
+
};
|
|
252
|
+
if (!Number.isInteger(request.unitAmountCents)) return {
|
|
253
|
+
error: 'Enter a price.'
|
|
254
|
+
};
|
|
255
|
+
if (!(request.quantity >= 1)) return {
|
|
256
|
+
error: 'Enter a quantity.'
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
line: CommerceModel.priceQuoteCustomLine(request)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (!request.productId) return {
|
|
263
|
+
error: 'Choose a product.'
|
|
264
|
+
};
|
|
265
|
+
if (!(request.quantity >= 1)) return {
|
|
266
|
+
error: 'Enter a quantity.'
|
|
267
|
+
};
|
|
268
|
+
const answer = CommerceModel.priceQuoteCatalogLine(productsById.get(request.productId), request);
|
|
269
|
+
return 'error' in answer ? {
|
|
270
|
+
error: answer.error
|
|
271
|
+
} : {
|
|
272
|
+
line: answer.line
|
|
273
|
+
};
|
|
274
|
+
}), [
|
|
275
|
+
form.lines,
|
|
276
|
+
productsById
|
|
277
|
+
]);
|
|
278
|
+
const discountCents = centsOf(form.discountValue);
|
|
279
|
+
const discount = form.discountKind === 'amount' && discountCents ? {
|
|
280
|
+
kind: 'amount',
|
|
281
|
+
amountCents: discountCents
|
|
282
|
+
} : form.discountKind === 'percent' && Number(form.discountValue) > 0 ? {
|
|
283
|
+
kind: 'percent',
|
|
284
|
+
pct: Math.min(100, Number(form.discountValue))
|
|
285
|
+
} : null;
|
|
286
|
+
const shippingCents = (_centsOf = centsOf(form.shipping)) != null ? _centsOf : 0;
|
|
287
|
+
const taxBasis = CommerceModel.quoteTaxBasisFromSettings(store == null ? void 0 : store.tax, {
|
|
288
|
+
country: form.shipTo.country.trim().toUpperCase(),
|
|
289
|
+
state: form.shipTo.state.trim()
|
|
290
|
+
});
|
|
291
|
+
const pricedLines = priced.flatMap((entry)=>'line' in entry && entry.line ? [
|
|
292
|
+
entry.line
|
|
293
|
+
] : []);
|
|
294
|
+
const totals = CommerceModel.computeQuoteTotals({
|
|
295
|
+
lines: pricedLines,
|
|
296
|
+
discount,
|
|
297
|
+
shippingCents,
|
|
298
|
+
tax: taxBasis
|
|
299
|
+
});
|
|
300
|
+
const problems = priced.some((entry)=>'error' in entry);
|
|
301
|
+
const savedService = (quote == null ? void 0 : (_quote_quote = quote.quote) == null ? void 0 : (_quote_quote_tax = _quote_quote.tax) == null ? void 0 : _quote_quote_tax.kind) === 'service' ? quote.quote.tax : null;
|
|
302
|
+
const save = useCallback(async ()=>{
|
|
303
|
+
setBusy(true);
|
|
304
|
+
try {
|
|
305
|
+
var _payload_number;
|
|
306
|
+
if (!attemptKey.current) {
|
|
307
|
+
var _ref;
|
|
308
|
+
var _globalThis_crypto_randomUUID, _globalThis_crypto;
|
|
309
|
+
attemptKey.current = (_ref = (_globalThis_crypto = globalThis.crypto) == null ? void 0 : (_globalThis_crypto_randomUUID = _globalThis_crypto.randomUUID) == null ? void 0 : _globalThis_crypto_randomUUID.call(_globalThis_crypto)) != null ? _ref : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
310
|
+
}
|
|
311
|
+
const response = await authorizedFetch(user, '/api/commerce/quote', {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: {
|
|
314
|
+
'Content-Type': 'application/json',
|
|
315
|
+
'Idempotency-Key': attemptKey.current
|
|
316
|
+
},
|
|
317
|
+
body: JSON.stringify(_extends({
|
|
318
|
+
action: quote ? 'update' : 'create',
|
|
319
|
+
hostId
|
|
320
|
+
}, quote ? {
|
|
321
|
+
quoteId: quote.$id,
|
|
322
|
+
baseUpdatedAtMs
|
|
323
|
+
} : {}, {
|
|
324
|
+
quote: quoteBody(_extends({}, form, {
|
|
325
|
+
requireProofApproval
|
|
326
|
+
}))
|
|
327
|
+
}))
|
|
328
|
+
});
|
|
329
|
+
const payload = await response.json().catch(()=>({}));
|
|
330
|
+
if (!response.ok) {
|
|
331
|
+
var _ref1;
|
|
332
|
+
return void enqueueSnackbar((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : 'The quote could not be saved', {
|
|
333
|
+
variant: 'error',
|
|
334
|
+
allowDuplicate: true
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
enqueueSnackbar(`${CommerceModel.formatQuoteNumber({
|
|
338
|
+
number: (_payload_number = payload.number) != null ? _payload_number : undefined
|
|
339
|
+
}, payload.quoteId)} saved`, {
|
|
340
|
+
variant: 'success',
|
|
341
|
+
persist: false
|
|
342
|
+
});
|
|
343
|
+
onClose();
|
|
344
|
+
} finally{
|
|
345
|
+
setBusy(false);
|
|
346
|
+
}
|
|
347
|
+
}, [
|
|
348
|
+
user,
|
|
349
|
+
quote,
|
|
350
|
+
hostId,
|
|
351
|
+
baseUpdatedAtMs,
|
|
352
|
+
form,
|
|
353
|
+
requireProofApproval,
|
|
354
|
+
enqueueSnackbar,
|
|
355
|
+
onClose
|
|
356
|
+
]);
|
|
357
|
+
const remove = useCallback(async ()=>{
|
|
358
|
+
if (!quote) return;
|
|
359
|
+
setBusy(true);
|
|
360
|
+
try {
|
|
361
|
+
const response = await authorizedFetch(user, '/api/commerce/quote', {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/json'
|
|
365
|
+
},
|
|
366
|
+
body: JSON.stringify({
|
|
367
|
+
action: 'delete',
|
|
368
|
+
hostId,
|
|
369
|
+
quoteId: quote.$id
|
|
370
|
+
})
|
|
371
|
+
});
|
|
372
|
+
const payload = await response.json().catch(()=>({}));
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
var _ref;
|
|
375
|
+
return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'The quote could not be deleted', {
|
|
376
|
+
variant: 'error',
|
|
377
|
+
allowDuplicate: true
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
enqueueSnackbar(`${CommerceModel.formatQuoteNumber(quote, quote.$id)} deleted`, {
|
|
381
|
+
variant: 'success',
|
|
382
|
+
persist: false
|
|
383
|
+
});
|
|
384
|
+
setConfirmingDelete(false);
|
|
385
|
+
onClose();
|
|
386
|
+
} finally{
|
|
387
|
+
setBusy(false);
|
|
388
|
+
}
|
|
389
|
+
}, [
|
|
390
|
+
user,
|
|
391
|
+
quote,
|
|
392
|
+
hostId,
|
|
393
|
+
enqueueSnackbar,
|
|
394
|
+
onClose
|
|
395
|
+
]);
|
|
396
|
+
const readOnly = !canEdit || busy;
|
|
397
|
+
const paid = quote ? !CommerceModel.isUnpaidQuote(quote) : false;
|
|
398
|
+
const title = quote ? CommerceModel.formatQuoteNumber(quote, quote.$id) : 'New quote';
|
|
399
|
+
const status = quote ? CommerceModel.quoteDisplayStatus(quote) : null;
|
|
400
|
+
const deletable = Boolean(quote) && !paid && ((_ref = quote == null ? void 0 : (_quote_quote1 = quote.quote) == null ? void 0 : _quote_quote1.stage) != null ? _ref : 'draft') === 'draft';
|
|
401
|
+
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
402
|
+
open: true,
|
|
403
|
+
onClose: busy ? undefined : onClose,
|
|
404
|
+
maxWidth: "md",
|
|
405
|
+
fullWidth: true,
|
|
406
|
+
scroll: "paper",
|
|
407
|
+
"aria-labelledby": "quote-editor-title",
|
|
408
|
+
children: [
|
|
409
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
410
|
+
id: "quote-editor-title",
|
|
411
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
412
|
+
direction: "row",
|
|
413
|
+
spacing: 1,
|
|
414
|
+
sx: {
|
|
415
|
+
alignItems: 'center'
|
|
416
|
+
},
|
|
417
|
+
children: [
|
|
418
|
+
/*#__PURE__*/ _jsx("span", {
|
|
419
|
+
children: title
|
|
420
|
+
}),
|
|
421
|
+
status ? /*#__PURE__*/ _jsx(Chip, {
|
|
422
|
+
size: "small",
|
|
423
|
+
variant: "outlined",
|
|
424
|
+
label: status.label,
|
|
425
|
+
color: CommerceModel.QUOTE_STATUS_COLOR[status.key]
|
|
426
|
+
}) : null
|
|
427
|
+
]
|
|
428
|
+
})
|
|
429
|
+
}),
|
|
430
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
431
|
+
dividers: true,
|
|
432
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
433
|
+
spacing: 3,
|
|
434
|
+
children: [
|
|
435
|
+
paid ? /*#__PURE__*/ _jsx(Alert, {
|
|
436
|
+
severity: "success",
|
|
437
|
+
children: (quote == null ? void 0 : (_quote_quote2 = quote.quote) == null ? void 0 : _quote_quote2.paidAtMs) ? `Paid ${new Date(quote.quote.paidAtMs).toLocaleDateString()}. It is an order now and can no longer be changed.` : 'This quote has been paid, so it is an order now and can no longer be changed.'
|
|
438
|
+
}) : null,
|
|
439
|
+
quote ? /*#__PURE__*/ _jsx(QuotePayLinkSection, {
|
|
440
|
+
hostId: hostId,
|
|
441
|
+
quoteId: quote.$id,
|
|
442
|
+
quote: quote,
|
|
443
|
+
canSend: canSend,
|
|
444
|
+
edited: edited
|
|
445
|
+
}) : null,
|
|
446
|
+
taxBasis.kind === 'none' && taxBasis.reason === 'undecided' ? /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
447
|
+
href: taxesHref != null ? taxesHref : undefined,
|
|
448
|
+
actionLabel: "Open Taxes",
|
|
449
|
+
children: 'This store has not chosen how it handles sales tax, so this quote shows no tax. Choose it in Taxes.'
|
|
450
|
+
}) : null,
|
|
451
|
+
/*#__PURE__*/ _jsx(QuoteCustomerSection, {
|
|
452
|
+
hostId: hostId,
|
|
453
|
+
form: form,
|
|
454
|
+
disabled: readOnly || paid,
|
|
455
|
+
onChange: (customer)=>update({
|
|
456
|
+
customer
|
|
457
|
+
})
|
|
458
|
+
}),
|
|
459
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
460
|
+
spacing: 1.5,
|
|
461
|
+
children: [
|
|
462
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
463
|
+
variant: "subtitle2",
|
|
464
|
+
children: 'Items'
|
|
465
|
+
}),
|
|
466
|
+
form.lines.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
467
|
+
variant: "body2",
|
|
468
|
+
color: "text.secondary",
|
|
469
|
+
children: 'Add products from your catalog, or type an item that is made to order.'
|
|
470
|
+
}) : null,
|
|
471
|
+
form.lines.map((line, index)=>/*#__PURE__*/ _jsx(QuoteLineEditor, {
|
|
472
|
+
line: line,
|
|
473
|
+
priced: priced[index],
|
|
474
|
+
products: products,
|
|
475
|
+
disabled: readOnly || paid,
|
|
476
|
+
onChange: (patch)=>updateLine(line.key, patch),
|
|
477
|
+
onRemove: ()=>update({
|
|
478
|
+
lines: form.lines.filter((entry)=>entry.key !== line.key)
|
|
479
|
+
})
|
|
480
|
+
}, line.key)),
|
|
481
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
482
|
+
direction: "row",
|
|
483
|
+
spacing: 1,
|
|
484
|
+
useFlexGap: true,
|
|
485
|
+
sx: {
|
|
486
|
+
flexWrap: 'wrap'
|
|
487
|
+
},
|
|
488
|
+
children: [
|
|
489
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
490
|
+
size: "small",
|
|
491
|
+
disabled: readOnly || paid || products.length === 0,
|
|
492
|
+
onClick: ()=>update({
|
|
493
|
+
lines: [
|
|
494
|
+
...form.lines,
|
|
495
|
+
{
|
|
496
|
+
key: nextLineKey(),
|
|
497
|
+
kind: 'catalog',
|
|
498
|
+
productId: '',
|
|
499
|
+
variantId: '',
|
|
500
|
+
quantity: '1',
|
|
501
|
+
modifiers: [],
|
|
502
|
+
description: '',
|
|
503
|
+
included: false
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
}),
|
|
507
|
+
children: 'Add product'
|
|
508
|
+
}),
|
|
509
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
510
|
+
size: "small",
|
|
511
|
+
disabled: readOnly || paid,
|
|
512
|
+
onClick: ()=>update({
|
|
513
|
+
lines: [
|
|
514
|
+
...form.lines,
|
|
515
|
+
{
|
|
516
|
+
key: nextLineKey(),
|
|
517
|
+
kind: 'custom',
|
|
518
|
+
name: '',
|
|
519
|
+
description: '',
|
|
520
|
+
sku: '',
|
|
521
|
+
price: '',
|
|
522
|
+
quantity: '1',
|
|
523
|
+
ships: true,
|
|
524
|
+
included: false
|
|
525
|
+
}
|
|
526
|
+
]
|
|
527
|
+
}),
|
|
528
|
+
children: 'Add custom item'
|
|
529
|
+
})
|
|
530
|
+
]
|
|
531
|
+
})
|
|
532
|
+
]
|
|
533
|
+
}),
|
|
534
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
535
|
+
direction: {
|
|
536
|
+
xs: 'column',
|
|
537
|
+
sm: 'row'
|
|
538
|
+
},
|
|
539
|
+
spacing: 2,
|
|
540
|
+
children: [
|
|
541
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
542
|
+
label: "Discount",
|
|
543
|
+
select: true,
|
|
544
|
+
size: "small",
|
|
545
|
+
value: form.discountKind,
|
|
546
|
+
disabled: readOnly || paid,
|
|
547
|
+
onChange: (event)=>update({
|
|
548
|
+
discountKind: event.target.value
|
|
549
|
+
}),
|
|
550
|
+
sx: mediumField,
|
|
551
|
+
children: [
|
|
552
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
553
|
+
value: "none",
|
|
554
|
+
children: 'None'
|
|
555
|
+
}),
|
|
556
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
557
|
+
value: "amount",
|
|
558
|
+
children: 'Amount off'
|
|
559
|
+
}),
|
|
560
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
561
|
+
value: "percent",
|
|
562
|
+
children: 'Percent off'
|
|
563
|
+
})
|
|
564
|
+
]
|
|
565
|
+
}),
|
|
566
|
+
form.discountKind !== 'none' ? /*#__PURE__*/ _jsx(TextField, {
|
|
567
|
+
label: form.discountKind === 'amount' ? 'Amount ($)' : 'Percent (%)',
|
|
568
|
+
size: "small",
|
|
569
|
+
value: form.discountValue,
|
|
570
|
+
disabled: readOnly || paid,
|
|
571
|
+
onChange: (event)=>update({
|
|
572
|
+
discountValue: event.target.value
|
|
573
|
+
}),
|
|
574
|
+
slotProps: {
|
|
575
|
+
htmlInput: {
|
|
576
|
+
inputMode: 'decimal'
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
error: form.discountKind === 'amount' ? centsOf(form.discountValue) === null : !(Number(form.discountValue) >= 0 && Number(form.discountValue) <= 100)
|
|
580
|
+
}) : null,
|
|
581
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
582
|
+
label: "Shipping ($)",
|
|
583
|
+
size: "small",
|
|
584
|
+
value: form.shipping,
|
|
585
|
+
disabled: readOnly || paid,
|
|
586
|
+
onChange: (event)=>update({
|
|
587
|
+
shipping: event.target.value
|
|
588
|
+
}),
|
|
589
|
+
slotProps: {
|
|
590
|
+
htmlInput: {
|
|
591
|
+
inputMode: 'decimal'
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
error: centsOf(form.shipping) === null,
|
|
595
|
+
helperText: "Leave empty when nothing ships."
|
|
596
|
+
})
|
|
597
|
+
]
|
|
598
|
+
}),
|
|
599
|
+
/*#__PURE__*/ _jsx(QuoteAddressFields, {
|
|
600
|
+
label: "Ship to",
|
|
601
|
+
value: form.shipTo,
|
|
602
|
+
disabled: readOnly || paid,
|
|
603
|
+
helperText: "Where the order goes. Store tax rates use its state and country.",
|
|
604
|
+
onChange: (shipTo)=>update({
|
|
605
|
+
shipTo
|
|
606
|
+
})
|
|
607
|
+
}),
|
|
608
|
+
/*#__PURE__*/ _jsx(QuoteAddressFields, {
|
|
609
|
+
label: "Bill to",
|
|
610
|
+
value: form.billTo,
|
|
611
|
+
disabled: readOnly || paid,
|
|
612
|
+
onChange: (billTo)=>update({
|
|
613
|
+
billTo
|
|
614
|
+
})
|
|
615
|
+
}),
|
|
616
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
617
|
+
spacing: 2,
|
|
618
|
+
children: [
|
|
619
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
620
|
+
label: "Notes for the customer",
|
|
621
|
+
multiline: true,
|
|
622
|
+
minRows: 2,
|
|
623
|
+
value: form.notes,
|
|
624
|
+
disabled: readOnly || paid,
|
|
625
|
+
onChange: (event)=>update({
|
|
626
|
+
notes: event.target.value
|
|
627
|
+
})
|
|
628
|
+
}),
|
|
629
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
630
|
+
label: "Terms",
|
|
631
|
+
multiline: true,
|
|
632
|
+
minRows: 2,
|
|
633
|
+
value: form.terms,
|
|
634
|
+
disabled: readOnly || paid,
|
|
635
|
+
onChange: (event)=>update({
|
|
636
|
+
terms: event.target.value
|
|
637
|
+
})
|
|
638
|
+
}),
|
|
639
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
640
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
641
|
+
checked: requireProofApproval,
|
|
642
|
+
disabled: readOnly || paid,
|
|
643
|
+
onChange: (event)=>update({
|
|
644
|
+
requireProofApproval: event.target.checked
|
|
645
|
+
})
|
|
646
|
+
}),
|
|
647
|
+
label: "Customers approve the proof before they can pay"
|
|
648
|
+
})
|
|
649
|
+
]
|
|
650
|
+
}),
|
|
651
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
652
|
+
/*#__PURE__*/ _jsx(QuoteTotalsSummary, {
|
|
653
|
+
totals: totals,
|
|
654
|
+
tax: taxBasis,
|
|
655
|
+
savedService: edited ? null : savedService,
|
|
656
|
+
savedTotals: edited ? null : (_ref1 = quote == null ? void 0 : quote.totals) != null ? _ref1 : null
|
|
657
|
+
})
|
|
658
|
+
]
|
|
659
|
+
})
|
|
660
|
+
}),
|
|
661
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
662
|
+
sx: {
|
|
663
|
+
flexWrap: 'wrap',
|
|
664
|
+
rowGap: 1
|
|
665
|
+
},
|
|
666
|
+
children: [
|
|
667
|
+
deletable ? /*#__PURE__*/ _jsx(BlockedControl, {
|
|
668
|
+
blocked: !canEdit,
|
|
669
|
+
reason: REFUSAL,
|
|
670
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
671
|
+
color: "error",
|
|
672
|
+
disabled: busy,
|
|
673
|
+
onClick: ()=>setConfirmingDelete(true),
|
|
674
|
+
sx: {
|
|
675
|
+
mr: 'auto'
|
|
676
|
+
},
|
|
677
|
+
children: 'Delete'
|
|
678
|
+
})
|
|
679
|
+
}) : null,
|
|
680
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
681
|
+
onClick: onClose,
|
|
682
|
+
disabled: busy,
|
|
683
|
+
children: paid || !canEdit ? 'Close' : 'Cancel'
|
|
684
|
+
}),
|
|
685
|
+
paid ? null : /*#__PURE__*/ _jsx(BlockedControl, {
|
|
686
|
+
blocked: !canEdit,
|
|
687
|
+
reason: REFUSAL,
|
|
688
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
689
|
+
variant: "contained",
|
|
690
|
+
disabled: busy || form.lines.length === 0 || problems,
|
|
691
|
+
onClick: ()=>void save(),
|
|
692
|
+
children: quote ? 'Save' : 'Create quote'
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
|
+
]
|
|
696
|
+
}),
|
|
697
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
698
|
+
open: confirmingDelete,
|
|
699
|
+
onClose: ()=>setConfirmingDelete(false),
|
|
700
|
+
maxWidth: "xs",
|
|
701
|
+
fullWidth: true,
|
|
702
|
+
children: [
|
|
703
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
704
|
+
children: `Delete ${title}?`
|
|
705
|
+
}),
|
|
706
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
707
|
+
children: /*#__PURE__*/ _jsx(DialogContentText, {
|
|
708
|
+
children: 'The quote and everything on it is removed. This can’t be undone.'
|
|
709
|
+
})
|
|
710
|
+
}),
|
|
711
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
712
|
+
children: [
|
|
713
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
714
|
+
onClick: ()=>setConfirmingDelete(false),
|
|
715
|
+
disabled: busy,
|
|
716
|
+
children: 'Cancel'
|
|
717
|
+
}),
|
|
718
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
719
|
+
color: "error",
|
|
720
|
+
variant: "contained",
|
|
721
|
+
disabled: busy,
|
|
722
|
+
onClick: ()=>void remove(),
|
|
723
|
+
children: 'Delete'
|
|
724
|
+
})
|
|
725
|
+
]
|
|
726
|
+
})
|
|
727
|
+
]
|
|
728
|
+
})
|
|
729
|
+
]
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
QuoteEditorDialog.displayName = 'QuoteEditorDialog';
|
|
733
|
+
/** Who the quote is for: a person the workspace keeps, or details typed here. */ function QuoteCustomerSection(props) {
|
|
734
|
+
const { hostId, form, disabled, onChange } = props;
|
|
735
|
+
const { data: user } = useUser();
|
|
736
|
+
const [text, setText] = useState('');
|
|
737
|
+
const [found, setFound] = useState([]);
|
|
738
|
+
const [available, setAvailable] = useState(true);
|
|
739
|
+
const ticket = useRef(0);
|
|
740
|
+
const customer = form.customer;
|
|
741
|
+
useEffect(()=>{
|
|
742
|
+
const typed = text.trim();
|
|
743
|
+
if (typed.length < 2 || !user) {
|
|
744
|
+
// Only when something is listed, so an effect that re-runs settles.
|
|
745
|
+
setFound((prev)=>prev.length ? [] : prev);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const mine = ++ticket.current;
|
|
749
|
+
const timer = window.setTimeout(()=>{
|
|
750
|
+
void authorizedFetch(user, '/api/commerce/quote', {
|
|
751
|
+
method: 'POST',
|
|
752
|
+
headers: {
|
|
753
|
+
'Content-Type': 'application/json'
|
|
754
|
+
},
|
|
755
|
+
body: JSON.stringify({
|
|
756
|
+
action: 'search-customers',
|
|
757
|
+
hostId,
|
|
758
|
+
text: typed
|
|
759
|
+
})
|
|
760
|
+
}).then((response)=>response.json().catch(()=>({}))).then((payload)=>{
|
|
761
|
+
if (mine !== ticket.current) return;
|
|
762
|
+
setAvailable((payload == null ? void 0 : payload.available) !== false);
|
|
763
|
+
setFound(Array.isArray(payload == null ? void 0 : payload.customers) ? payload.customers : []);
|
|
764
|
+
}).catch(()=>undefined);
|
|
765
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
766
|
+
return ()=>window.clearTimeout(timer);
|
|
767
|
+
}, [
|
|
768
|
+
text,
|
|
769
|
+
user,
|
|
770
|
+
hostId
|
|
771
|
+
]);
|
|
772
|
+
const pick = (person)=>{
|
|
773
|
+
var _person_email, _person_phone;
|
|
774
|
+
onChange({
|
|
775
|
+
record: person.record,
|
|
776
|
+
name: person.name,
|
|
777
|
+
company: person.company,
|
|
778
|
+
email: (_person_email = person.email) != null ? _person_email : '',
|
|
779
|
+
phone: (_person_phone = person.phone) != null ? _person_phone : ''
|
|
780
|
+
});
|
|
781
|
+
setText('');
|
|
782
|
+
setFound([]);
|
|
783
|
+
};
|
|
784
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
785
|
+
spacing: 1.5,
|
|
786
|
+
children: [
|
|
787
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
788
|
+
variant: "subtitle2",
|
|
789
|
+
children: 'Customer'
|
|
790
|
+
}),
|
|
791
|
+
customer.record ? /*#__PURE__*/ _jsxs(Stack, {
|
|
792
|
+
direction: "row",
|
|
793
|
+
spacing: 1,
|
|
794
|
+
sx: {
|
|
795
|
+
alignItems: 'center'
|
|
796
|
+
},
|
|
797
|
+
children: [
|
|
798
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
799
|
+
color: "primary",
|
|
800
|
+
variant: "outlined",
|
|
801
|
+
label: customer.name || customer.email || 'Contact',
|
|
802
|
+
onDelete: disabled ? undefined : ()=>onChange(_extends({}, customer, {
|
|
803
|
+
record: null
|
|
804
|
+
}))
|
|
805
|
+
}),
|
|
806
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
807
|
+
variant: "caption",
|
|
808
|
+
color: "text.secondary",
|
|
809
|
+
children: 'From your contacts'
|
|
810
|
+
})
|
|
811
|
+
]
|
|
812
|
+
}) : available ? /*#__PURE__*/ _jsxs(Box, {
|
|
813
|
+
children: [
|
|
814
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
815
|
+
size: "small",
|
|
816
|
+
fullWidth: true,
|
|
817
|
+
label: "Find a contact",
|
|
818
|
+
placeholder: "Name, email or phone",
|
|
819
|
+
value: text,
|
|
820
|
+
disabled: disabled,
|
|
821
|
+
onChange: (event)=>setText(event.target.value)
|
|
822
|
+
}),
|
|
823
|
+
found.length ? /*#__PURE__*/ _jsx(Paper, {
|
|
824
|
+
variant: "outlined",
|
|
825
|
+
sx: {
|
|
826
|
+
mt: 0.5
|
|
827
|
+
},
|
|
828
|
+
children: /*#__PURE__*/ _jsx(List, {
|
|
829
|
+
dense: true,
|
|
830
|
+
disablePadding: true,
|
|
831
|
+
children: found.map((person)=>/*#__PURE__*/ _jsx(ListItemButton, {
|
|
832
|
+
onClick: ()=>pick(person),
|
|
833
|
+
children: /*#__PURE__*/ _jsx(ListItemText, {
|
|
834
|
+
primary: person.name || person.email || 'Contact',
|
|
835
|
+
secondary: [
|
|
836
|
+
person.company,
|
|
837
|
+
person.email,
|
|
838
|
+
person.phone
|
|
839
|
+
].filter(Boolean).join(' · ')
|
|
840
|
+
})
|
|
841
|
+
}, `${person.record.kind}:${person.record.id}`))
|
|
842
|
+
})
|
|
843
|
+
}) : null
|
|
844
|
+
]
|
|
845
|
+
}) : null,
|
|
846
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
847
|
+
direction: {
|
|
848
|
+
xs: 'column',
|
|
849
|
+
sm: 'row'
|
|
850
|
+
},
|
|
851
|
+
spacing: 2,
|
|
852
|
+
children: [
|
|
853
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
854
|
+
size: "small",
|
|
855
|
+
fullWidth: true,
|
|
856
|
+
label: "Name",
|
|
857
|
+
value: customer.name,
|
|
858
|
+
disabled: disabled,
|
|
859
|
+
onChange: (event)=>onChange(_extends({}, customer, {
|
|
860
|
+
name: event.target.value
|
|
861
|
+
}))
|
|
862
|
+
}),
|
|
863
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
864
|
+
size: "small",
|
|
865
|
+
fullWidth: true,
|
|
866
|
+
label: "Company",
|
|
867
|
+
value: customer.company,
|
|
868
|
+
disabled: disabled,
|
|
869
|
+
onChange: (event)=>onChange(_extends({}, customer, {
|
|
870
|
+
company: event.target.value
|
|
871
|
+
}))
|
|
872
|
+
})
|
|
873
|
+
]
|
|
874
|
+
}),
|
|
875
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
876
|
+
direction: {
|
|
877
|
+
xs: 'column',
|
|
878
|
+
sm: 'row'
|
|
879
|
+
},
|
|
880
|
+
spacing: 2,
|
|
881
|
+
children: [
|
|
882
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
883
|
+
size: "small",
|
|
884
|
+
fullWidth: true,
|
|
885
|
+
label: "Email",
|
|
886
|
+
type: "email",
|
|
887
|
+
value: customer.email,
|
|
888
|
+
disabled: disabled,
|
|
889
|
+
onChange: (event)=>onChange(_extends({}, customer, {
|
|
890
|
+
email: event.target.value
|
|
891
|
+
}))
|
|
892
|
+
}),
|
|
893
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
894
|
+
size: "small",
|
|
895
|
+
fullWidth: true,
|
|
896
|
+
label: "Phone",
|
|
897
|
+
value: customer.phone,
|
|
898
|
+
disabled: disabled,
|
|
899
|
+
onChange: (event)=>onChange(_extends({}, customer, {
|
|
900
|
+
phone: event.target.value
|
|
901
|
+
}))
|
|
902
|
+
})
|
|
903
|
+
]
|
|
904
|
+
})
|
|
905
|
+
]
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
/** One address on the quote. */ function QuoteAddressFields(props) {
|
|
909
|
+
const { label, value, disabled, helperText, onChange } = props;
|
|
910
|
+
const field = (key, fieldLabel, extra = {})=>/*#__PURE__*/ _jsx(TextField, _extends({
|
|
911
|
+
size: "small",
|
|
912
|
+
fullWidth: true,
|
|
913
|
+
label: fieldLabel,
|
|
914
|
+
value: value[key],
|
|
915
|
+
disabled: disabled,
|
|
916
|
+
onChange: (event)=>onChange(_extends({}, value, {
|
|
917
|
+
[key]: event.target.value
|
|
918
|
+
}))
|
|
919
|
+
}, extra));
|
|
920
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
921
|
+
spacing: 1.5,
|
|
922
|
+
children: [
|
|
923
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
924
|
+
variant: "subtitle2",
|
|
925
|
+
children: label
|
|
926
|
+
}),
|
|
927
|
+
helperText ? /*#__PURE__*/ _jsx(Typography, {
|
|
928
|
+
variant: "caption",
|
|
929
|
+
color: "text.secondary",
|
|
930
|
+
children: helperText
|
|
931
|
+
}) : null,
|
|
932
|
+
field('name', 'Name'),
|
|
933
|
+
field('line1', 'Address'),
|
|
934
|
+
field('line2', 'Apartment, suite, unit'),
|
|
935
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
936
|
+
direction: {
|
|
937
|
+
xs: 'column',
|
|
938
|
+
sm: 'row'
|
|
939
|
+
},
|
|
940
|
+
spacing: 2,
|
|
941
|
+
children: [
|
|
942
|
+
field('city', 'City'),
|
|
943
|
+
field('state', 'State or region'),
|
|
944
|
+
field('postalCode', 'Postal code'),
|
|
945
|
+
field('country', 'Country code', {
|
|
946
|
+
placeholder: 'US',
|
|
947
|
+
slotProps: {
|
|
948
|
+
htmlInput: {
|
|
949
|
+
maxLength: 2
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
})
|
|
953
|
+
]
|
|
954
|
+
})
|
|
955
|
+
]
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
/** One line of the quote: a product with its options and add-ons, or a typed item. */ function QuoteLineEditor(props) {
|
|
959
|
+
var _lifted_variants_;
|
|
960
|
+
const { line, priced, products, disabled, onChange, onRemove } = props;
|
|
961
|
+
const product = line.kind === 'catalog' ? products.find((entry)=>String(entry.$id) === line.productId) : undefined;
|
|
962
|
+
const lifted = product ? CommerceModel.liftLegacyProduct(product) : null;
|
|
963
|
+
const groups = lifted ? CommerceModel.productModifierGroups(lifted) : [];
|
|
964
|
+
const lineCents = (priced == null ? void 0 : priced.line) ? CommerceModel.quoteLineCents(priced.line) : 0;
|
|
965
|
+
return /*#__PURE__*/ _jsx(Paper, {
|
|
966
|
+
variant: "outlined",
|
|
967
|
+
sx: {
|
|
968
|
+
p: 2
|
|
969
|
+
},
|
|
970
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
971
|
+
spacing: 1.5,
|
|
972
|
+
children: [
|
|
973
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
974
|
+
direction: "row",
|
|
975
|
+
spacing: 1,
|
|
976
|
+
sx: {
|
|
977
|
+
alignItems: 'flex-start'
|
|
978
|
+
},
|
|
979
|
+
children: [
|
|
980
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
981
|
+
spacing: 1.5,
|
|
982
|
+
sx: {
|
|
983
|
+
flex: 1,
|
|
984
|
+
minWidth: 0
|
|
985
|
+
},
|
|
986
|
+
children: [
|
|
987
|
+
line.kind === 'catalog' ? /*#__PURE__*/ _jsxs(Stack, {
|
|
988
|
+
direction: {
|
|
989
|
+
xs: 'column',
|
|
990
|
+
sm: 'row'
|
|
991
|
+
},
|
|
992
|
+
spacing: 2,
|
|
993
|
+
children: [
|
|
994
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
995
|
+
label: "Product",
|
|
996
|
+
select: true,
|
|
997
|
+
size: "small",
|
|
998
|
+
fullWidth: true,
|
|
999
|
+
value: line.productId,
|
|
1000
|
+
disabled: disabled,
|
|
1001
|
+
onChange: (event)=>onChange({
|
|
1002
|
+
productId: event.target.value,
|
|
1003
|
+
variantId: '',
|
|
1004
|
+
modifiers: []
|
|
1005
|
+
}),
|
|
1006
|
+
children: products.map((entry)=>{
|
|
1007
|
+
var _entry_name;
|
|
1008
|
+
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
1009
|
+
value: String(entry.$id),
|
|
1010
|
+
children: String((_entry_name = entry.name) != null ? _entry_name : entry.$id)
|
|
1011
|
+
}, String(entry.$id));
|
|
1012
|
+
})
|
|
1013
|
+
}),
|
|
1014
|
+
lifted && lifted.variants.length > 1 ? /*#__PURE__*/ _jsx(TextField, {
|
|
1015
|
+
label: "Option",
|
|
1016
|
+
select: true,
|
|
1017
|
+
size: "small",
|
|
1018
|
+
fullWidth: true,
|
|
1019
|
+
value: line.variantId || ((_lifted_variants_ = lifted.variants[0]) == null ? void 0 : _lifted_variants_.id) || '',
|
|
1020
|
+
disabled: disabled,
|
|
1021
|
+
onChange: (event)=>onChange({
|
|
1022
|
+
variantId: event.target.value
|
|
1023
|
+
}),
|
|
1024
|
+
children: lifted.variants.map((variant)=>{
|
|
1025
|
+
var _variant_options;
|
|
1026
|
+
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
1027
|
+
value: variant.id,
|
|
1028
|
+
children: Object.values((_variant_options = variant.options) != null ? _variant_options : {}).join(' / ') || 'Default'
|
|
1029
|
+
}, variant.id);
|
|
1030
|
+
})
|
|
1031
|
+
}) : null
|
|
1032
|
+
]
|
|
1033
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
1034
|
+
direction: {
|
|
1035
|
+
xs: 'column',
|
|
1036
|
+
sm: 'row'
|
|
1037
|
+
},
|
|
1038
|
+
spacing: 2,
|
|
1039
|
+
children: [
|
|
1040
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1041
|
+
label: "Item",
|
|
1042
|
+
size: "small",
|
|
1043
|
+
fullWidth: true,
|
|
1044
|
+
value: line.name,
|
|
1045
|
+
disabled: disabled,
|
|
1046
|
+
onChange: (event)=>onChange({
|
|
1047
|
+
name: event.target.value
|
|
1048
|
+
})
|
|
1049
|
+
}),
|
|
1050
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1051
|
+
label: "SKU (optional)",
|
|
1052
|
+
size: "small",
|
|
1053
|
+
value: line.sku,
|
|
1054
|
+
disabled: disabled,
|
|
1055
|
+
onChange: (event)=>onChange({
|
|
1056
|
+
sku: event.target.value
|
|
1057
|
+
})
|
|
1058
|
+
})
|
|
1059
|
+
]
|
|
1060
|
+
}),
|
|
1061
|
+
groups.map((group)=>{
|
|
1062
|
+
var _chosen_;
|
|
1063
|
+
const chosen = line.kind === 'catalog' ? line.modifiers.filter((pick)=>pick.groupId === group.id).map((pick)=>pick.optionId) : [];
|
|
1064
|
+
return /*#__PURE__*/ _jsxs(TextField, {
|
|
1065
|
+
label: CommerceModel.modifierGroupRequired(group) ? group.name : `${group.name} (optional)`,
|
|
1066
|
+
select: true,
|
|
1067
|
+
size: "small",
|
|
1068
|
+
fullWidth: true,
|
|
1069
|
+
value: group.max > 1 ? chosen : (_chosen_ = chosen[0]) != null ? _chosen_ : '',
|
|
1070
|
+
disabled: disabled,
|
|
1071
|
+
slotProps: {
|
|
1072
|
+
select: {
|
|
1073
|
+
multiple: group.max > 1
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
onChange: (event)=>{
|
|
1077
|
+
const raw = event.target.value;
|
|
1078
|
+
const picked = (Array.isArray(raw) ? raw : raw ? [
|
|
1079
|
+
raw
|
|
1080
|
+
] : []).map(String);
|
|
1081
|
+
if (line.kind !== 'catalog') return;
|
|
1082
|
+
onChange({
|
|
1083
|
+
modifiers: [
|
|
1084
|
+
...line.modifiers.filter((pick)=>pick.groupId !== group.id),
|
|
1085
|
+
...picked.map((optionId)=>({
|
|
1086
|
+
groupId: group.id,
|
|
1087
|
+
optionId
|
|
1088
|
+
}))
|
|
1089
|
+
]
|
|
1090
|
+
});
|
|
1091
|
+
},
|
|
1092
|
+
children: [
|
|
1093
|
+
group.max > 1 ? null : /*#__PURE__*/ _jsx(MenuItem, {
|
|
1094
|
+
value: "",
|
|
1095
|
+
children: 'None'
|
|
1096
|
+
}),
|
|
1097
|
+
group.options.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1098
|
+
value: option.id,
|
|
1099
|
+
children: option.priceCents ? `${option.name} (+${money(option.priceCents)})` : option.name
|
|
1100
|
+
}, option.id))
|
|
1101
|
+
]
|
|
1102
|
+
}, group.id);
|
|
1103
|
+
}),
|
|
1104
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1105
|
+
label: "Description (optional)",
|
|
1106
|
+
size: "small",
|
|
1107
|
+
fullWidth: true,
|
|
1108
|
+
multiline: true,
|
|
1109
|
+
value: line.description,
|
|
1110
|
+
disabled: disabled,
|
|
1111
|
+
onChange: (event)=>onChange({
|
|
1112
|
+
description: event.target.value
|
|
1113
|
+
})
|
|
1114
|
+
}),
|
|
1115
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1116
|
+
direction: {
|
|
1117
|
+
xs: 'column',
|
|
1118
|
+
sm: 'row'
|
|
1119
|
+
},
|
|
1120
|
+
spacing: 2,
|
|
1121
|
+
sx: {
|
|
1122
|
+
alignItems: {
|
|
1123
|
+
sm: 'center'
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
children: [
|
|
1127
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
1128
|
+
label: "Quantity",
|
|
1129
|
+
size: "small",
|
|
1130
|
+
value: line.quantity,
|
|
1131
|
+
disabled: disabled,
|
|
1132
|
+
onChange: (event)=>onChange({
|
|
1133
|
+
quantity: event.target.value.replace(/[^0-9]/g, '')
|
|
1134
|
+
}),
|
|
1135
|
+
slotProps: {
|
|
1136
|
+
htmlInput: {
|
|
1137
|
+
inputMode: 'numeric'
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
sx: narrowField
|
|
1141
|
+
}),
|
|
1142
|
+
line.kind === 'custom' ? /*#__PURE__*/ _jsx(TextField, {
|
|
1143
|
+
label: "Unit price ($)",
|
|
1144
|
+
size: "small",
|
|
1145
|
+
value: line.included ? '' : line.price,
|
|
1146
|
+
disabled: disabled || line.included,
|
|
1147
|
+
onChange: (event)=>onChange({
|
|
1148
|
+
price: event.target.value
|
|
1149
|
+
}),
|
|
1150
|
+
slotProps: {
|
|
1151
|
+
htmlInput: {
|
|
1152
|
+
inputMode: 'decimal'
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
error: !line.included && centsOf(line.price) === null,
|
|
1156
|
+
sx: mediumField
|
|
1157
|
+
}) : null,
|
|
1158
|
+
line.kind === 'custom' ? /*#__PURE__*/ _jsx(FormControlLabel, {
|
|
1159
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
1160
|
+
checked: line.ships,
|
|
1161
|
+
disabled: disabled,
|
|
1162
|
+
onChange: (event)=>onChange({
|
|
1163
|
+
ships: event.target.checked
|
|
1164
|
+
})
|
|
1165
|
+
}),
|
|
1166
|
+
label: "Ships"
|
|
1167
|
+
}) : null,
|
|
1168
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
1169
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
1170
|
+
checked: line.included,
|
|
1171
|
+
disabled: disabled,
|
|
1172
|
+
onChange: (event)=>onChange({
|
|
1173
|
+
included: event.target.checked
|
|
1174
|
+
})
|
|
1175
|
+
}),
|
|
1176
|
+
label: "Included at no charge"
|
|
1177
|
+
})
|
|
1178
|
+
]
|
|
1179
|
+
})
|
|
1180
|
+
]
|
|
1181
|
+
}),
|
|
1182
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1183
|
+
sx: {
|
|
1184
|
+
alignItems: 'flex-end',
|
|
1185
|
+
flexShrink: 0
|
|
1186
|
+
},
|
|
1187
|
+
children: [
|
|
1188
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1189
|
+
size: "small",
|
|
1190
|
+
color: "inherit",
|
|
1191
|
+
disabled: disabled,
|
|
1192
|
+
onClick: onRemove,
|
|
1193
|
+
children: 'Remove'
|
|
1194
|
+
}),
|
|
1195
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1196
|
+
variant: "body2",
|
|
1197
|
+
sx: {
|
|
1198
|
+
fontWeight: 'fontWeightMedium'
|
|
1199
|
+
},
|
|
1200
|
+
children: line.included ? 'Included' : money(lineCents)
|
|
1201
|
+
}),
|
|
1202
|
+
(priced == null ? void 0 : priced.line) && !line.included && priced.line.quantity > 1 ? /*#__PURE__*/ _jsx(Typography, {
|
|
1203
|
+
variant: "caption",
|
|
1204
|
+
color: "text.secondary",
|
|
1205
|
+
children: `${money(priced.line.unitAmountCents)} each`
|
|
1206
|
+
}) : null
|
|
1207
|
+
]
|
|
1208
|
+
})
|
|
1209
|
+
]
|
|
1210
|
+
}),
|
|
1211
|
+
(priced == null ? void 0 : priced.error) ? /*#__PURE__*/ _jsx(Typography, {
|
|
1212
|
+
variant: "caption",
|
|
1213
|
+
color: "error",
|
|
1214
|
+
children: priced.error
|
|
1215
|
+
}) : null
|
|
1216
|
+
]
|
|
1217
|
+
})
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
/** The quote's totals, as the customer will read them. */ function QuoteTotalsSummary(props) {
|
|
1221
|
+
const { totals, tax, savedService, savedTotals } = props;
|
|
1222
|
+
const shown = savedService && savedTotals ? _extends({}, totals, savedTotals) : totals;
|
|
1223
|
+
const taxLabel = CommerceModel.describeQuoteTax(savedService != null ? savedService : tax);
|
|
1224
|
+
const row = (label, value, strong = false)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
1225
|
+
direction: "row",
|
|
1226
|
+
sx: {
|
|
1227
|
+
justifyContent: 'space-between'
|
|
1228
|
+
},
|
|
1229
|
+
children: [
|
|
1230
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1231
|
+
variant: strong ? 'subtitle1' : 'body2',
|
|
1232
|
+
color: strong ? 'text.primary' : 'text.secondary',
|
|
1233
|
+
children: label
|
|
1234
|
+
}),
|
|
1235
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1236
|
+
variant: strong ? 'subtitle1' : 'body2',
|
|
1237
|
+
children: value
|
|
1238
|
+
})
|
|
1239
|
+
]
|
|
1240
|
+
});
|
|
1241
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
1242
|
+
spacing: 0.5,
|
|
1243
|
+
sx: (theme)=>({
|
|
1244
|
+
alignSelf: {
|
|
1245
|
+
sm: 'flex-end'
|
|
1246
|
+
},
|
|
1247
|
+
minWidth: {
|
|
1248
|
+
sm: theme.spacing(40)
|
|
1249
|
+
}
|
|
1250
|
+
}),
|
|
1251
|
+
children: [
|
|
1252
|
+
row('Items', money(shown.itemsCents)),
|
|
1253
|
+
shown.discountCents ? row('Discount', `−${money(shown.discountCents)}`) : null,
|
|
1254
|
+
shown.shippingCents ? row('Shipping', money(shown.shippingCents)) : null,
|
|
1255
|
+
tax.kind === 'at-payment' && !savedService ? /*#__PURE__*/ _jsx(Typography, {
|
|
1256
|
+
variant: "body2",
|
|
1257
|
+
color: "text.secondary",
|
|
1258
|
+
children: taxLabel
|
|
1259
|
+
}) : row(taxLabel, money(shown.taxCents)),
|
|
1260
|
+
row(totals.taxAtPayment && !savedService ? 'Total before tax' : 'Total', money(shown.totalCents), true)
|
|
1261
|
+
]
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
export default QuoteEditorDialog;
|
|
1265
|
+
|
|
1266
|
+
//# sourceMappingURL=quote-editor-dialog.component.js.map
|