@aglyn/plugins-commerce 1.0.0-beta.289 → 1.0.0-beta.291
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 +4 -2
- package/package.json +13 -13
- package/src/lib/components/commerce-console-page.js +2 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
- package/src/lib/components/console/commerce-pdf-download.js +53 -0
- package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
- package/src/lib/components/console/host-orders-card.component.js +3 -2
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/host-quotes-card.component.js +20 -4
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
- package/src/lib/components/console/invoice-send.component.d.ts +44 -0
- package/src/lib/components/console/invoice-send.component.js +237 -0
- package/src/lib/components/console/invoice-send.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
- package/src/lib/components/console/order-detail-dialog.component.js +9 -2
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-invoice.component.d.ts +15 -4
- package/src/lib/components/console/order-invoice.component.js +93 -12
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +154 -5
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/quote-proofs.component.d.ts +48 -0
- package/src/lib/components/console/quote-proofs.component.js +694 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -0
- package/src/lib/components/console/quote-send.component.d.ts +42 -0
- package/src/lib/components/console/quote-send.component.js +307 -0
- package/src/lib/components/console/quote-send.component.js.map +1 -0
- package/src/lib/components/console/quote-start.component.d.ts +45 -0
- package/src/lib/components/console/quote-start.component.js +182 -0
- package/src/lib/components/console/quote-start.component.js.map +1 -0
- package/src/lib/components/product-options-field-body.d.ts +44 -0
- package/src/lib/components/product-options-field-body.js +260 -0
- package/src/lib/components/product-options-field-body.js.map +1 -0
- package/src/lib/components/product-options-field.d.ts +32 -0
- package/src/lib/components/product-options-field.js +128 -0
- package/src/lib/components/product-options-field.js.map +1 -0
- package/src/lib/components/quote-page-body.js +67 -20
- package/src/lib/components/quote-page-body.js.map +1 -1
- package/src/lib/components/quote-proof-panel.d.ts +42 -0
- package/src/lib/components/quote-proof-panel.js +436 -0
- package/src/lib/components/quote-proof-panel.js.map +1 -0
- package/src/lib/model/commerce-invoices.d.ts +132 -0
- package/src/lib/model/commerce-invoices.js +332 -0
- package/src/lib/model/commerce-invoices.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +29 -2
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-product-field.d.ts +23 -0
- package/src/lib/model/commerce-product-field.js +26 -0
- package/src/lib/model/commerce-product-field.js.map +1 -0
- package/src/lib/model/commerce-quote-email.d.ts +67 -0
- package/src/lib/model/commerce-quote-email.js +154 -0
- package/src/lib/model/commerce-quote-email.js.map +1 -0
- package/src/lib/model/commerce-quote-pay.d.ts +76 -6
- package/src/lib/model/commerce-quote-pay.js +105 -39
- package/src/lib/model/commerce-quote-pay.js.map +1 -1
- package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
- package/src/lib/model/commerce-quote-proofs.js +348 -0
- package/src/lib/model/commerce-quote-proofs.js.map +1 -0
- package/src/lib/model/commerce-quote-request.d.ts +93 -0
- package/src/lib/model/commerce-quote-request.js +207 -0
- package/src/lib/model/commerce-quote-request.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +140 -3
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.js +19 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +115 -70
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/commerce-pdf.d.ts +104 -0
- package/src/lib/server/commerce-pdf.js +310 -0
- package/src/lib/server/commerce-pdf.js.map +1 -0
- package/src/lib/server/draft-order.js +23 -3
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/invoice-email.d.ts +76 -0
- package/src/lib/server/invoice-email.js +183 -0
- package/src/lib/server/invoice-email.js.map +1 -0
- package/src/lib/server/order-invoice-send.d.ts +43 -0
- package/src/lib/server/order-invoice-send.js +218 -0
- package/src/lib/server/order-invoice-send.js.map +1 -0
- package/src/lib/server/order-print.d.ts +6 -0
- package/src/lib/server/order-print.js +22 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/order-status.d.ts +11 -0
- package/src/lib/server/order-status.js +44 -8
- package/src/lib/server/order-status.js.map +1 -1
- package/src/lib/server/pdf-response.d.ts +31 -0
- package/src/lib/server/pdf-response.js +30 -0
- package/src/lib/server/pdf-response.js.map +1 -0
- package/src/lib/server/person-eraser.d.ts +3 -2
- package/src/lib/server/person-eraser.js +25 -6
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-reader-splash.d.ts +2 -18
- package/src/lib/server/pos-reader-splash.js +8 -93
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/quote-email.d.ts +85 -0
- package/src/lib/server/quote-email.js +211 -0
- package/src/lib/server/quote-email.js.map +1 -0
- package/src/lib/server/quote-link-token.d.ts +11 -0
- package/src/lib/server/quote-link-token.js +21 -0
- package/src/lib/server/quote-link-token.js.map +1 -1
- package/src/lib/server/quote-pay.d.ts +38 -6
- package/src/lib/server/quote-pay.js +115 -53
- package/src/lib/server/quote-pay.js.map +1 -1
- package/src/lib/server/quote-proofs.d.ts +51 -0
- package/src/lib/server/quote-proofs.js +500 -0
- package/src/lib/server/quote-proofs.js.map +1 -0
- package/src/lib/server/quote-uploads.d.ts +33 -0
- package/src/lib/server/quote-uploads.js +29 -0
- package/src/lib/server/quote-uploads.js.map +1 -0
- package/src/lib/server/quote.d.ts +25 -0
- package/src/lib/server/quote.js +638 -8
- package/src/lib/server/quote.js.map +1 -1
- package/src/lib/server/site-image-source.d.ts +38 -0
- package/src/lib/server/site-image-source.js +130 -0
- package/src/lib/server/site-image-source.js.map +1 -0
- package/src/lib/server/site-page-resolver.js +2 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +24 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +8 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/tenant-emails.js +243 -0
- package/src/lib/tenant-emails.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +82 -0
- package/src/lib/utils/commerce-document-pdf.js +613 -0
- package/src/lib/utils/commerce-document-pdf.js.map +1 -0
- package/src/lib/utils/invoice-email-content.d.ts +45 -0
- package/src/lib/utils/invoice-email-content.js +126 -0
- package/src/lib/utils/invoice-email-content.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +52 -3
- package/src/lib/utils/order-invoice.js +124 -64
- package/src/lib/utils/order-invoice.js.map +1 -1
- package/src/lib/utils/quote-email-content.d.ts +62 -0
- package/src/lib/utils/quote-email-content.js +154 -0
- package/src/lib/utils/quote-email-content.js.map +1 -0
|
@@ -0,0 +1,694 @@
|
|
|
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 { commerceSitePermissionRefusal } from "../../model/plugin-permissions.js";
|
|
21
|
+
import { checkVisitorUploadSelection, formatVisitorUploadBytes, visitorUploadIsInline, VISITOR_UPLOAD_ACCEPT, VISITOR_UPLOAD_MAX_BYTES } from "@aglyn/aglyn/app-utils/visitor-upload";
|
|
22
|
+
import { sendVisitorUpload } from "@aglyn/aglyn/app-utils/visitor-upload-client";
|
|
23
|
+
import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
24
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
25
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
26
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
27
|
+
import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, Paper, Stack, Typography } from "@mui/material";
|
|
28
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
29
|
+
const REFUSAL = commerceSitePermissionRefusal('commerce.quotes');
|
|
30
|
+
async function postQuote(user, body) {
|
|
31
|
+
const response = await authorizedFetch(user, '/api/commerce/quote', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify(body)
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
ok: response.ok,
|
|
40
|
+
payload: await response.json().catch(()=>({}))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** One of the quote's files as a blob, fetched with the member's token; null when refused. */ async function fetchQuoteFile(user, body) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await authorizedFetch(user, '/api/commerce/quote', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json'
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify(_extends({
|
|
51
|
+
action: 'file'
|
|
52
|
+
}, body))
|
|
53
|
+
});
|
|
54
|
+
return response.ok ? await response.blob() : null;
|
|
55
|
+
} catch (unused) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function dateTime(atMs) {
|
|
60
|
+
return atMs ? new Date(atMs).toLocaleString(undefined, {
|
|
61
|
+
dateStyle: 'medium',
|
|
62
|
+
timeStyle: 'short'
|
|
63
|
+
}) : '';
|
|
64
|
+
}
|
|
65
|
+
/** A picture of an image proof, loaded with the member's token. */ function ProofThumbnail(props) {
|
|
66
|
+
const { user, hostId, quoteId, file, revision } = props;
|
|
67
|
+
const [src, setSrc] = useState(null);
|
|
68
|
+
useEffect(()=>{
|
|
69
|
+
let live = true;
|
|
70
|
+
let url = '';
|
|
71
|
+
void fetchQuoteFile(user, _extends({
|
|
72
|
+
hostId,
|
|
73
|
+
quoteId,
|
|
74
|
+
fileId: file.id
|
|
75
|
+
}, revision ? {
|
|
76
|
+
revision
|
|
77
|
+
} : {})).then((blob)=>{
|
|
78
|
+
if (!live || !blob) return;
|
|
79
|
+
url = URL.createObjectURL(blob);
|
|
80
|
+
setSrc(url);
|
|
81
|
+
});
|
|
82
|
+
return ()=>{
|
|
83
|
+
live = false;
|
|
84
|
+
if (url) URL.revokeObjectURL(url);
|
|
85
|
+
};
|
|
86
|
+
}, [
|
|
87
|
+
user,
|
|
88
|
+
hostId,
|
|
89
|
+
quoteId,
|
|
90
|
+
file.id,
|
|
91
|
+
revision
|
|
92
|
+
]);
|
|
93
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
94
|
+
sx: (theme)=>({
|
|
95
|
+
width: theme.spacing(16),
|
|
96
|
+
height: theme.spacing(12),
|
|
97
|
+
display: 'flex',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
justifyContent: 'center',
|
|
100
|
+
bgcolor: 'action.hover',
|
|
101
|
+
borderRadius: 1,
|
|
102
|
+
overflow: 'hidden'
|
|
103
|
+
}),
|
|
104
|
+
children: src ? /*#__PURE__*/ _jsx(Box, {
|
|
105
|
+
component: "img",
|
|
106
|
+
src: src,
|
|
107
|
+
alt: file.name,
|
|
108
|
+
sx: {
|
|
109
|
+
maxWidth: 1,
|
|
110
|
+
maxHeight: 1,
|
|
111
|
+
objectFit: 'contain'
|
|
112
|
+
}
|
|
113
|
+
}) : /*#__PURE__*/ _jsx(CircularProgress, {
|
|
114
|
+
size: 20,
|
|
115
|
+
"aria-label": `Loading ${file.name}`
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** A file: a picture for an image, a chip naming it for anything else; both download it. */ function QuoteFileTile(props) {
|
|
120
|
+
const { file, revision, onDownload, onRemove, removeBlocked } = props;
|
|
121
|
+
const image = visitorUploadIsInline(file.contentType);
|
|
122
|
+
return /*#__PURE__*/ _jsx(Paper, {
|
|
123
|
+
variant: "outlined",
|
|
124
|
+
sx: {
|
|
125
|
+
p: 1
|
|
126
|
+
},
|
|
127
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
128
|
+
spacing: 1,
|
|
129
|
+
sx: (theme)=>({
|
|
130
|
+
width: image ? theme.spacing(16) : 'auto',
|
|
131
|
+
maxWidth: theme.spacing(32)
|
|
132
|
+
}),
|
|
133
|
+
children: [
|
|
134
|
+
image ? /*#__PURE__*/ _jsx(ProofThumbnail, _extends({}, props)) : null,
|
|
135
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
136
|
+
size: "small",
|
|
137
|
+
variant: "outlined",
|
|
138
|
+
label: `${file.name} · ${formatVisitorUploadBytes(file.size)}`,
|
|
139
|
+
onClick: ()=>onDownload(file, revision),
|
|
140
|
+
title: `Download ${file.name}`
|
|
141
|
+
}),
|
|
142
|
+
onRemove ? /*#__PURE__*/ _jsx(BlockedControl, {
|
|
143
|
+
blocked: Boolean(removeBlocked),
|
|
144
|
+
reason: REFUSAL,
|
|
145
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
146
|
+
size: "small",
|
|
147
|
+
color: "inherit",
|
|
148
|
+
onClick: onRemove,
|
|
149
|
+
"aria-label": `Remove ${file.name}`,
|
|
150
|
+
children: 'Remove'
|
|
151
|
+
})
|
|
152
|
+
}) : null
|
|
153
|
+
]
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** What the customer said, with any files they sent. */ function QuoteResponseNote(props) {
|
|
158
|
+
var _response_files;
|
|
159
|
+
const { response, tile } = props;
|
|
160
|
+
if (response.action === 'approve') {
|
|
161
|
+
var _response_userAgent;
|
|
162
|
+
return /*#__PURE__*/ _jsx(Alert, {
|
|
163
|
+
severity: "success",
|
|
164
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
165
|
+
spacing: 0.5,
|
|
166
|
+
children: [
|
|
167
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
168
|
+
variant: "body2",
|
|
169
|
+
children: `Approved by ${response.name || 'the customer'} on ${dateTime(response.atMs)}.`
|
|
170
|
+
}),
|
|
171
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
172
|
+
variant: "caption",
|
|
173
|
+
color: "text.secondary",
|
|
174
|
+
children: [
|
|
175
|
+
response.ip ? `From ${response.ip}` : '',
|
|
176
|
+
(_response_userAgent = response.userAgent) != null ? _response_userAgent : ''
|
|
177
|
+
].filter(Boolean).join(' · ')
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return /*#__PURE__*/ _jsx(Alert, {
|
|
184
|
+
severity: "warning",
|
|
185
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
186
|
+
spacing: 1,
|
|
187
|
+
children: [
|
|
188
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
189
|
+
variant: "body2",
|
|
190
|
+
children: `Changes requested on ${dateTime(response.atMs)}:`
|
|
191
|
+
}),
|
|
192
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
193
|
+
variant: "body2",
|
|
194
|
+
sx: {
|
|
195
|
+
whiteSpace: 'pre-line'
|
|
196
|
+
},
|
|
197
|
+
children: response.comment
|
|
198
|
+
}),
|
|
199
|
+
((_response_files = response.files) == null ? void 0 : _response_files.length) ? /*#__PURE__*/ _jsx(Stack, {
|
|
200
|
+
direction: "row",
|
|
201
|
+
spacing: 1,
|
|
202
|
+
useFlexGap: true,
|
|
203
|
+
sx: {
|
|
204
|
+
flexWrap: 'wrap'
|
|
205
|
+
},
|
|
206
|
+
children: response.files.map((file)=>tile(file))
|
|
207
|
+
}) : null
|
|
208
|
+
]
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* A quote's proofs and revisions (AGL-4096), in its editor: the current
|
|
214
|
+
* revision's proofs (a picture for an image, a chip for a PDF or a design
|
|
215
|
+
* file), Add proof, what the customer last said with any files they sent,
|
|
216
|
+
* "Start revision N+1", and every earlier revision as it was kept.
|
|
217
|
+
*/ export function QuoteProofsSection(props) {
|
|
218
|
+
var _ref;
|
|
219
|
+
const { hostId, quoteId, quote, canEdit, onEdited } = props;
|
|
220
|
+
const { data: user } = useUser();
|
|
221
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
222
|
+
const section = quote.quote;
|
|
223
|
+
const revision = Math.max(1, Number((_ref = section == null ? void 0 : section.revision) != null ? _ref : 1) || 1);
|
|
224
|
+
const proofs = CommerceModel.currentQuoteProofs(section);
|
|
225
|
+
const latest = CommerceModel.latestQuoteResponse(section);
|
|
226
|
+
const unpaid = CommerceModel.isUnpaidQuote(quote);
|
|
227
|
+
const addRefusal = CommerceModel.quoteProofChangeRefusal(quote, 'add');
|
|
228
|
+
const removable = CommerceModel.quoteProofChangeRefusal(quote, 'remove') === null;
|
|
229
|
+
const [sending, setSending] = useState(null);
|
|
230
|
+
const [busy, setBusy] = useState(false);
|
|
231
|
+
const [confirmingRevision, setConfirmingRevision] = useState(false);
|
|
232
|
+
const [history, setHistory] = useState(null);
|
|
233
|
+
const [historyError, setHistoryError] = useState('');
|
|
234
|
+
const picker = useRef(null);
|
|
235
|
+
const loadHistory = useCallback(async ()=>{
|
|
236
|
+
setHistoryError('');
|
|
237
|
+
const answer = await postQuote(user, {
|
|
238
|
+
action: 'revisions',
|
|
239
|
+
hostId,
|
|
240
|
+
quoteId
|
|
241
|
+
}).catch(()=>null);
|
|
242
|
+
if (!(answer == null ? void 0 : answer.ok)) {
|
|
243
|
+
var _ref;
|
|
244
|
+
var _answer_payload;
|
|
245
|
+
setHistoryError(String((_ref = answer == null ? void 0 : (_answer_payload = answer.payload) == null ? void 0 : _answer_payload.error) != null ? _ref : 'Earlier revisions could not be loaded.'));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
setHistory(Array.isArray(answer.payload['revisions']) ? answer.payload['revisions'] : []);
|
|
249
|
+
}, [
|
|
250
|
+
user,
|
|
251
|
+
hostId,
|
|
252
|
+
quoteId
|
|
253
|
+
]);
|
|
254
|
+
// Earlier revisions are read when there are some, and again after one is added.
|
|
255
|
+
useEffect(()=>{
|
|
256
|
+
if (revision > 1) void loadHistory();
|
|
257
|
+
}, [
|
|
258
|
+
revision,
|
|
259
|
+
loadHistory
|
|
260
|
+
]);
|
|
261
|
+
const download = useCallback(async (file, fromRevision)=>{
|
|
262
|
+
const blob = await fetchQuoteFile(user, _extends({
|
|
263
|
+
hostId,
|
|
264
|
+
quoteId,
|
|
265
|
+
fileId: file.id
|
|
266
|
+
}, fromRevision ? {
|
|
267
|
+
revision: fromRevision
|
|
268
|
+
} : {}));
|
|
269
|
+
if (!blob) return void enqueueSnackbar('The file could not be downloaded', {
|
|
270
|
+
variant: 'error',
|
|
271
|
+
allowDuplicate: true
|
|
272
|
+
});
|
|
273
|
+
const url = URL.createObjectURL(blob);
|
|
274
|
+
const link = document.createElement('a');
|
|
275
|
+
link.href = url;
|
|
276
|
+
link.download = file.name;
|
|
277
|
+
link.click();
|
|
278
|
+
setTimeout(()=>URL.revokeObjectURL(url), 1000);
|
|
279
|
+
}, [
|
|
280
|
+
user,
|
|
281
|
+
hostId,
|
|
282
|
+
quoteId,
|
|
283
|
+
enqueueSnackbar
|
|
284
|
+
]);
|
|
285
|
+
const addProofs = useCallback(async (files)=>{
|
|
286
|
+
const problem = checkVisitorUploadSelection(files, proofs.length, CommerceModel.QUOTE_MAX_PROOFS);
|
|
287
|
+
if (problem) return void enqueueSnackbar(problem, {
|
|
288
|
+
variant: 'error',
|
|
289
|
+
allowDuplicate: true
|
|
290
|
+
});
|
|
291
|
+
setSending({
|
|
292
|
+
done: 0,
|
|
293
|
+
total: files.length
|
|
294
|
+
});
|
|
295
|
+
try {
|
|
296
|
+
for (const [index, file] of files.entries()){
|
|
297
|
+
let stamp = 0;
|
|
298
|
+
const sent = await sendVisitorUpload({
|
|
299
|
+
file,
|
|
300
|
+
requestTicket: async (request)=>{
|
|
301
|
+
var _answer_payload_error;
|
|
302
|
+
const answer = await postQuote(user, {
|
|
303
|
+
action: 'proof-ticket',
|
|
304
|
+
hostId,
|
|
305
|
+
quoteId,
|
|
306
|
+
file: request
|
|
307
|
+
});
|
|
308
|
+
return answer.ok ? answer.payload : {
|
|
309
|
+
error: String((_answer_payload_error = answer.payload['error']) != null ? _answer_payload_error : 'The proof could not be sent')
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
finalize: async (ticket)=>{
|
|
313
|
+
var _answer_payload_error;
|
|
314
|
+
const answer = await postQuote(user, {
|
|
315
|
+
action: 'proof-finalize',
|
|
316
|
+
hostId,
|
|
317
|
+
quoteId,
|
|
318
|
+
ticket
|
|
319
|
+
});
|
|
320
|
+
if (!answer.ok) return {
|
|
321
|
+
error: String((_answer_payload_error = answer.payload['error']) != null ? _answer_payload_error : 'The proof could not be kept')
|
|
322
|
+
};
|
|
323
|
+
stamp = Number(answer.payload['updatedAtMs']) || 0;
|
|
324
|
+
return {
|
|
325
|
+
file: answer.payload['proof']
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
if ('error' in sent) {
|
|
330
|
+
enqueueSnackbar(sent.error, {
|
|
331
|
+
variant: 'error',
|
|
332
|
+
allowDuplicate: true
|
|
333
|
+
});
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
if (stamp) onEdited(stamp);
|
|
337
|
+
setSending({
|
|
338
|
+
done: index + 1,
|
|
339
|
+
total: files.length
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
} finally{
|
|
343
|
+
setSending(null);
|
|
344
|
+
}
|
|
345
|
+
}, [
|
|
346
|
+
user,
|
|
347
|
+
hostId,
|
|
348
|
+
quoteId,
|
|
349
|
+
proofs.length,
|
|
350
|
+
onEdited,
|
|
351
|
+
enqueueSnackbar
|
|
352
|
+
]);
|
|
353
|
+
const removeProof = useCallback(async (proofId)=>{
|
|
354
|
+
setBusy(true);
|
|
355
|
+
try {
|
|
356
|
+
const answer = await postQuote(user, {
|
|
357
|
+
action: 'proof-remove',
|
|
358
|
+
hostId,
|
|
359
|
+
quoteId,
|
|
360
|
+
proofId
|
|
361
|
+
});
|
|
362
|
+
if (!answer.ok) {
|
|
363
|
+
var _answer_payload_error;
|
|
364
|
+
return void enqueueSnackbar(String((_answer_payload_error = answer.payload['error']) != null ? _answer_payload_error : 'The proof could not be removed'), {
|
|
365
|
+
variant: 'error',
|
|
366
|
+
allowDuplicate: true
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
onEdited(Number(answer.payload['updatedAtMs']) || 0);
|
|
370
|
+
} finally{
|
|
371
|
+
setBusy(false);
|
|
372
|
+
}
|
|
373
|
+
}, [
|
|
374
|
+
user,
|
|
375
|
+
hostId,
|
|
376
|
+
quoteId,
|
|
377
|
+
onEdited,
|
|
378
|
+
enqueueSnackbar
|
|
379
|
+
]);
|
|
380
|
+
const startRevision = useCallback(async ()=>{
|
|
381
|
+
setBusy(true);
|
|
382
|
+
try {
|
|
383
|
+
const answer = await postQuote(user, {
|
|
384
|
+
action: 'start-revision',
|
|
385
|
+
hostId,
|
|
386
|
+
quoteId
|
|
387
|
+
});
|
|
388
|
+
if (!answer.ok) {
|
|
389
|
+
var _answer_payload_error;
|
|
390
|
+
return void enqueueSnackbar(String((_answer_payload_error = answer.payload['error']) != null ? _answer_payload_error : 'The revision could not be started'), {
|
|
391
|
+
variant: 'error',
|
|
392
|
+
allowDuplicate: true
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
onEdited(Number(answer.payload['updatedAtMs']) || 0);
|
|
396
|
+
enqueueSnackbar(`Revision ${Number(answer.payload['revision']) || revision + 1} started`, {
|
|
397
|
+
variant: 'success',
|
|
398
|
+
persist: false
|
|
399
|
+
});
|
|
400
|
+
setConfirmingRevision(false);
|
|
401
|
+
} finally{
|
|
402
|
+
setBusy(false);
|
|
403
|
+
}
|
|
404
|
+
}, [
|
|
405
|
+
user,
|
|
406
|
+
hostId,
|
|
407
|
+
quoteId,
|
|
408
|
+
revision,
|
|
409
|
+
onEdited,
|
|
410
|
+
enqueueSnackbar
|
|
411
|
+
]);
|
|
412
|
+
const tile = (file, fromRevision, onRemove)=>/*#__PURE__*/ _jsx(QuoteFileTile, {
|
|
413
|
+
user: user,
|
|
414
|
+
hostId: hostId,
|
|
415
|
+
quoteId: quoteId,
|
|
416
|
+
file: file,
|
|
417
|
+
revision: fromRevision,
|
|
418
|
+
onDownload: (picked, at)=>void download(picked, at),
|
|
419
|
+
onRemove: onRemove,
|
|
420
|
+
removeBlocked: !canEdit
|
|
421
|
+
}, `${fromRevision != null ? fromRevision : 'current'}-${file.id}`);
|
|
422
|
+
const earlier = (history != null ? history : []).filter((entry)=>Number(entry.revision) < revision).reverse();
|
|
423
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
424
|
+
spacing: 1.5,
|
|
425
|
+
children: [
|
|
426
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
427
|
+
spacing: 0.5,
|
|
428
|
+
children: [
|
|
429
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
430
|
+
variant: "subtitle2",
|
|
431
|
+
children: `Proofs · Revision ${revision}`
|
|
432
|
+
}),
|
|
433
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
434
|
+
variant: "body2",
|
|
435
|
+
color: "text.secondary",
|
|
436
|
+
children: (section == null ? void 0 : section.requireProofApproval) ? 'The customer sees these on the quote page and approves them before they can pay.' : 'The customer sees these on the quote page, and can approve them or ask for changes. Paying doesn’t wait on it.'
|
|
437
|
+
})
|
|
438
|
+
]
|
|
439
|
+
}),
|
|
440
|
+
latest ? /*#__PURE__*/ _jsx(QuoteResponseNote, {
|
|
441
|
+
response: latest,
|
|
442
|
+
tile: (file)=>tile(file)
|
|
443
|
+
}) : null,
|
|
444
|
+
proofs.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
445
|
+
direction: "row",
|
|
446
|
+
spacing: 1,
|
|
447
|
+
useFlexGap: true,
|
|
448
|
+
sx: {
|
|
449
|
+
flexWrap: 'wrap'
|
|
450
|
+
},
|
|
451
|
+
children: proofs.map((proof)=>tile(proof, undefined, removable ? ()=>void removeProof(proof.id) : undefined))
|
|
452
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
453
|
+
variant: "body2",
|
|
454
|
+
color: "text.secondary",
|
|
455
|
+
children: 'No proofs on this revision yet. Add an image, a PDF or a design file for the customer to approve.'
|
|
456
|
+
}),
|
|
457
|
+
unpaid ? /*#__PURE__*/ _jsxs(Stack, {
|
|
458
|
+
direction: "row",
|
|
459
|
+
spacing: 1,
|
|
460
|
+
useFlexGap: true,
|
|
461
|
+
sx: {
|
|
462
|
+
flexWrap: 'wrap',
|
|
463
|
+
alignItems: 'center'
|
|
464
|
+
},
|
|
465
|
+
children: [
|
|
466
|
+
/*#__PURE__*/ _jsx("input", {
|
|
467
|
+
ref: picker,
|
|
468
|
+
type: "file",
|
|
469
|
+
multiple: true,
|
|
470
|
+
hidden: true,
|
|
471
|
+
accept: VISITOR_UPLOAD_ACCEPT,
|
|
472
|
+
"data-testid": "quote-proof-picker",
|
|
473
|
+
onChange: (event)=>{
|
|
474
|
+
var _event_target_files;
|
|
475
|
+
const files = Array.from((_event_target_files = event.target.files) != null ? _event_target_files : []);
|
|
476
|
+
event.target.value = '';
|
|
477
|
+
if (files.length) void addProofs(files);
|
|
478
|
+
}
|
|
479
|
+
}),
|
|
480
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
481
|
+
blocked: !canEdit || Boolean(addRefusal),
|
|
482
|
+
reason: !canEdit ? REFUSAL : addRefusal ? CommerceModel.quoteProofRefusalMessage(addRefusal) : '',
|
|
483
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
484
|
+
variant: "outlined",
|
|
485
|
+
size: "small",
|
|
486
|
+
disabled: busy || Boolean(sending),
|
|
487
|
+
onClick: ()=>{
|
|
488
|
+
var _picker_current;
|
|
489
|
+
return (_picker_current = picker.current) == null ? void 0 : _picker_current.click();
|
|
490
|
+
},
|
|
491
|
+
children: sending ? `Sending ${sending.done + 1} of ${sending.total}…` : 'Add proof'
|
|
492
|
+
})
|
|
493
|
+
}),
|
|
494
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
495
|
+
blocked: !canEdit,
|
|
496
|
+
reason: REFUSAL,
|
|
497
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
498
|
+
size: "small",
|
|
499
|
+
disabled: busy || Boolean(sending),
|
|
500
|
+
onClick: ()=>setConfirmingRevision(true),
|
|
501
|
+
children: `Start revision ${revision + 1}`
|
|
502
|
+
})
|
|
503
|
+
}),
|
|
504
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
505
|
+
variant: "caption",
|
|
506
|
+
color: "text.secondary",
|
|
507
|
+
children: `Up to ${CommerceModel.QUOTE_MAX_PROOFS} files, ${formatVisitorUploadBytes(VISITOR_UPLOAD_MAX_BYTES)} each.`
|
|
508
|
+
})
|
|
509
|
+
]
|
|
510
|
+
}) : null,
|
|
511
|
+
revision > 1 ? /*#__PURE__*/ _jsxs(Stack, {
|
|
512
|
+
spacing: 1.5,
|
|
513
|
+
children: [
|
|
514
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
515
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
516
|
+
variant: "subtitle2",
|
|
517
|
+
children: 'Earlier revisions'
|
|
518
|
+
}),
|
|
519
|
+
historyError ? /*#__PURE__*/ _jsx(Alert, {
|
|
520
|
+
severity: "error",
|
|
521
|
+
children: historyError
|
|
522
|
+
}) : null,
|
|
523
|
+
history === null && !historyError ? /*#__PURE__*/ _jsx(CircularProgress, {
|
|
524
|
+
size: 20,
|
|
525
|
+
"aria-label": "Loading earlier revisions"
|
|
526
|
+
}) : null,
|
|
527
|
+
earlier.map((entry)=>{
|
|
528
|
+
var _CommerceModel_QUOTE_STAGE_LABELS_entry_stage, _CommerceModel_QUOTE_STATUS_COLOR_entry_stage;
|
|
529
|
+
return /*#__PURE__*/ _jsx(Paper, {
|
|
530
|
+
variant: "outlined",
|
|
531
|
+
sx: {
|
|
532
|
+
p: 2
|
|
533
|
+
},
|
|
534
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
535
|
+
spacing: 1,
|
|
536
|
+
children: [
|
|
537
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
538
|
+
direction: "row",
|
|
539
|
+
spacing: 1,
|
|
540
|
+
sx: {
|
|
541
|
+
alignItems: 'center',
|
|
542
|
+
flexWrap: 'wrap',
|
|
543
|
+
rowGap: 0.5
|
|
544
|
+
},
|
|
545
|
+
children: [
|
|
546
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
547
|
+
variant: "body2",
|
|
548
|
+
sx: {
|
|
549
|
+
fontWeight: 'fontWeightMedium'
|
|
550
|
+
},
|
|
551
|
+
children: `Revision ${entry.revision}`
|
|
552
|
+
}),
|
|
553
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
554
|
+
size: "small",
|
|
555
|
+
variant: "outlined",
|
|
556
|
+
label: (_CommerceModel_QUOTE_STAGE_LABELS_entry_stage = CommerceModel.QUOTE_STAGE_LABELS[entry.stage]) != null ? _CommerceModel_QUOTE_STAGE_LABELS_entry_stage : entry.stage,
|
|
557
|
+
color: (_CommerceModel_QUOTE_STATUS_COLOR_entry_stage = CommerceModel.QUOTE_STATUS_COLOR[entry.stage]) != null ? _CommerceModel_QUOTE_STATUS_COLOR_entry_stage : 'default'
|
|
558
|
+
}),
|
|
559
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
560
|
+
variant: "caption",
|
|
561
|
+
color: "text.secondary",
|
|
562
|
+
children: `Replaced ${dateTime(entry.closedAtMs)}`
|
|
563
|
+
})
|
|
564
|
+
]
|
|
565
|
+
}),
|
|
566
|
+
entry.proofs.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
567
|
+
direction: "row",
|
|
568
|
+
spacing: 1,
|
|
569
|
+
useFlexGap: true,
|
|
570
|
+
sx: {
|
|
571
|
+
flexWrap: 'wrap'
|
|
572
|
+
},
|
|
573
|
+
children: entry.proofs.map((proof)=>tile(proof, entry.revision))
|
|
574
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
575
|
+
variant: "body2",
|
|
576
|
+
color: "text.secondary",
|
|
577
|
+
children: 'No proofs.'
|
|
578
|
+
}),
|
|
579
|
+
entry.responses.map((response, index)=>/*#__PURE__*/ _jsx(QuoteResponseNote, {
|
|
580
|
+
response: response,
|
|
581
|
+
tile: (file)=>tile(file, entry.revision)
|
|
582
|
+
}, index))
|
|
583
|
+
]
|
|
584
|
+
})
|
|
585
|
+
}, entry.revision);
|
|
586
|
+
})
|
|
587
|
+
]
|
|
588
|
+
}) : null,
|
|
589
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
590
|
+
open: confirmingRevision,
|
|
591
|
+
onClose: ()=>setConfirmingRevision(false),
|
|
592
|
+
maxWidth: "xs",
|
|
593
|
+
fullWidth: true,
|
|
594
|
+
children: [
|
|
595
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
596
|
+
children: `Start revision ${revision + 1}?`
|
|
597
|
+
}),
|
|
598
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
599
|
+
children: /*#__PURE__*/ _jsx(DialogContentText, {
|
|
600
|
+
children: `Revision ${revision} is kept as it is now, with its proofs and the customer’s answers. ` + `Revision ${revision + 1} starts with no proofs, ready for new ones and any changes to the quote.`
|
|
601
|
+
})
|
|
602
|
+
}),
|
|
603
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
604
|
+
children: [
|
|
605
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
606
|
+
onClick: ()=>setConfirmingRevision(false),
|
|
607
|
+
disabled: busy,
|
|
608
|
+
children: 'Cancel'
|
|
609
|
+
}),
|
|
610
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
611
|
+
variant: "contained",
|
|
612
|
+
disabled: busy,
|
|
613
|
+
onClick: ()=>void startRevision(),
|
|
614
|
+
children: `Start revision ${revision + 1}`
|
|
615
|
+
})
|
|
616
|
+
]
|
|
617
|
+
})
|
|
618
|
+
]
|
|
619
|
+
})
|
|
620
|
+
]
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
QuoteProofsSection.displayName = 'QuoteProofsSection';
|
|
624
|
+
export default QuoteProofsSection;
|
|
625
|
+
/**
|
|
626
|
+
* The files a quote's request came with (AGL-4098), as the customer's files:
|
|
627
|
+
* a picture for an image, a chip naming anything else, each downloaded
|
|
628
|
+
* through the quote's own route. They stay where the request keeps them, so a
|
|
629
|
+
* request deleted from the Inbox takes them with it.
|
|
630
|
+
*/ export function QuoteRequestFilesSection(props) {
|
|
631
|
+
var _quote_quote, _quote_quote1;
|
|
632
|
+
const { hostId, quoteId, quote } = props;
|
|
633
|
+
const { data: user } = useUser();
|
|
634
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
635
|
+
const request = (_quote_quote = quote.quote) == null ? void 0 : _quote_quote.request;
|
|
636
|
+
const files = CommerceModel.quoteRequestFileViews((_quote_quote1 = quote.quote) == null ? void 0 : _quote_quote1.requestFiles);
|
|
637
|
+
const download = useCallback(async (file)=>{
|
|
638
|
+
const blob = await fetchQuoteFile(user, {
|
|
639
|
+
hostId,
|
|
640
|
+
quoteId,
|
|
641
|
+
fileId: file.id
|
|
642
|
+
});
|
|
643
|
+
if (!blob) {
|
|
644
|
+
return void enqueueSnackbar('The file could not be downloaded. The request may have been deleted from the Inbox.', {
|
|
645
|
+
variant: 'error',
|
|
646
|
+
allowDuplicate: true
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const url = URL.createObjectURL(blob);
|
|
650
|
+
const link = document.createElement('a');
|
|
651
|
+
link.href = url;
|
|
652
|
+
link.download = file.name;
|
|
653
|
+
link.click();
|
|
654
|
+
setTimeout(()=>URL.revokeObjectURL(url), 1000);
|
|
655
|
+
}, [
|
|
656
|
+
user,
|
|
657
|
+
hostId,
|
|
658
|
+
quoteId,
|
|
659
|
+
enqueueSnackbar
|
|
660
|
+
]);
|
|
661
|
+
if (!request || !user) return null;
|
|
662
|
+
const when = request.receivedAtMs ? ` on ${dateTime(request.receivedAtMs)}` : '';
|
|
663
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
664
|
+
spacing: 1,
|
|
665
|
+
children: [
|
|
666
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
667
|
+
variant: "subtitle2",
|
|
668
|
+
children: 'From the request'
|
|
669
|
+
}),
|
|
670
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
671
|
+
variant: "body2",
|
|
672
|
+
color: "text.secondary",
|
|
673
|
+
children: `Sent through “${request.formName}”${when}.` + (files.length ? ' The files they sent:' : ' They sent no files.')
|
|
674
|
+
}),
|
|
675
|
+
files.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
676
|
+
direction: "row",
|
|
677
|
+
spacing: 1,
|
|
678
|
+
useFlexGap: true,
|
|
679
|
+
sx: {
|
|
680
|
+
flexWrap: 'wrap'
|
|
681
|
+
},
|
|
682
|
+
children: files.map((file)=>/*#__PURE__*/ _jsx(QuoteFileTile, {
|
|
683
|
+
user: user,
|
|
684
|
+
hostId: hostId,
|
|
685
|
+
quoteId: quoteId,
|
|
686
|
+
file: file,
|
|
687
|
+
onDownload: (picked)=>void download(picked)
|
|
688
|
+
}, file.id))
|
|
689
|
+
}) : null
|
|
690
|
+
]
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
//# sourceMappingURL=quote-proofs.component.js.map
|