@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,436 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2026 Aglyn LLC
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/ import { checkVisitorUploadSelection, describeVisitorUploadTypes, formatVisitorUploadBytes, VISITOR_UPLOAD_ACCEPT, VISITOR_UPLOAD_HONEYPOT_FIELD, VISITOR_UPLOAD_MAX_BYTES, VISITOR_UPLOAD_MAX_FILES } from "@aglyn/aglyn/app-utils/visitor-upload";
|
|
19
|
+
import { sendVisitorUpload } from "@aglyn/aglyn/app-utils/visitor-upload-client";
|
|
20
|
+
import Alert from "@mui/material/Alert";
|
|
21
|
+
import Box from "@mui/material/Box";
|
|
22
|
+
import Button from "@mui/material/Button";
|
|
23
|
+
import Chip from "@mui/material/Chip";
|
|
24
|
+
import Paper from "@mui/material/Paper";
|
|
25
|
+
import Stack from "@mui/material/Stack";
|
|
26
|
+
import TextField from "@mui/material/TextField";
|
|
27
|
+
import Typography from "@mui/material/Typography";
|
|
28
|
+
import { useCallback, useRef, useState } from "react";
|
|
29
|
+
function dateLabel(atMs) {
|
|
30
|
+
return new Date(atMs).toLocaleDateString(undefined, {
|
|
31
|
+
month: 'short',
|
|
32
|
+
day: 'numeric',
|
|
33
|
+
year: 'numeric'
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const newKey = ()=>{
|
|
37
|
+
var _ref;
|
|
38
|
+
var _globalThis_crypto_randomUUID, _globalThis_crypto;
|
|
39
|
+
return (_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)}`;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The quote's proofs on the customer's page (AGL-4096): each image shown,
|
|
43
|
+
* every other file a download, and while the quote is open for an answer,
|
|
44
|
+
* "Approve this proof" (under a typed name) and "Request changes" (a comment
|
|
45
|
+
* and any artwork). The latest answer is the one that counts, so a customer
|
|
46
|
+
* who asked for changes can still approve, and the panel says what they last
|
|
47
|
+
* said. A paid quote shows its proofs and nothing to press.
|
|
48
|
+
*/ export function QuoteProofPanel(props) {
|
|
49
|
+
const { view, hostId, quoteId, token, siteFetch, onView, onReload, onReplaced } = props;
|
|
50
|
+
const [mode, setMode] = useState('idle');
|
|
51
|
+
const [name, setName] = useState('');
|
|
52
|
+
const [comment, setComment] = useState('');
|
|
53
|
+
const [files, setFiles] = useState([]);
|
|
54
|
+
const [uploading, setUploading] = useState(null);
|
|
55
|
+
const [sending, setSending] = useState(false);
|
|
56
|
+
const [error, setError] = useState('');
|
|
57
|
+
// Whatever a bot types into the hidden field, sent as typed.
|
|
58
|
+
const [honeypot, setHoneypot] = useState('');
|
|
59
|
+
const picker = useRef(null);
|
|
60
|
+
const attempt = useRef('');
|
|
61
|
+
const link = `hostId=${encodeURIComponent(hostId)}&q=${encodeURIComponent(quoteId)}&t=${encodeURIComponent(token)}`;
|
|
62
|
+
const proofHref = (id)=>`/api/commerce/quote-proof?${link}&file=${encodeURIComponent(id)}`;
|
|
63
|
+
const business = view.storeName || 'the business';
|
|
64
|
+
const post = useCallback(async (path, body, headers = {})=>{
|
|
65
|
+
const response = await siteFetch(path, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: _extends({
|
|
68
|
+
'Content-Type': 'application/json'
|
|
69
|
+
}, headers),
|
|
70
|
+
body: JSON.stringify(_extends({
|
|
71
|
+
hostId,
|
|
72
|
+
q: quoteId,
|
|
73
|
+
t: token
|
|
74
|
+
}, body))
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
response,
|
|
78
|
+
payload: await response.json().catch(()=>({}))
|
|
79
|
+
};
|
|
80
|
+
}, [
|
|
81
|
+
siteFetch,
|
|
82
|
+
hostId,
|
|
83
|
+
quoteId,
|
|
84
|
+
token
|
|
85
|
+
]);
|
|
86
|
+
const attach = useCallback(async (picked)=>{
|
|
87
|
+
setError('');
|
|
88
|
+
const problem = checkVisitorUploadSelection(picked, files.length);
|
|
89
|
+
if (problem) return void setError(problem);
|
|
90
|
+
setUploading({
|
|
91
|
+
done: 0,
|
|
92
|
+
total: picked.length
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
for (const [index, file] of picked.entries()){
|
|
96
|
+
const sent = await sendVisitorUpload({
|
|
97
|
+
file,
|
|
98
|
+
requestTicket: async (request)=>{
|
|
99
|
+
var _payload_error;
|
|
100
|
+
const { response, payload } = await post('/api/commerce/quote-upload', {
|
|
101
|
+
action: 'ticket',
|
|
102
|
+
file: request,
|
|
103
|
+
[VISITOR_UPLOAD_HONEYPOT_FIELD]: honeypot
|
|
104
|
+
});
|
|
105
|
+
return response.ok && payload['ticket'] ? payload : {
|
|
106
|
+
error: String((_payload_error = payload['error']) != null ? _payload_error : 'The file could not be sent. Try again.')
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
finalize: async (ticket)=>{
|
|
110
|
+
var _payload_error;
|
|
111
|
+
const { response, payload } = await post('/api/commerce/quote-upload', {
|
|
112
|
+
action: 'finalize',
|
|
113
|
+
ticket
|
|
114
|
+
});
|
|
115
|
+
return response.ok && payload['file'] ? {
|
|
116
|
+
file: payload['file']
|
|
117
|
+
} : {
|
|
118
|
+
error: String((_payload_error = payload['error']) != null ? _payload_error : 'The file could not be sent. Try again.')
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
if ('error' in sent) {
|
|
123
|
+
setError(sent.error);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
setFiles((prev)=>[
|
|
127
|
+
...prev,
|
|
128
|
+
sent.file
|
|
129
|
+
]);
|
|
130
|
+
setUploading({
|
|
131
|
+
done: index + 1,
|
|
132
|
+
total: picked.length
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} finally{
|
|
136
|
+
setUploading(null);
|
|
137
|
+
}
|
|
138
|
+
}, [
|
|
139
|
+
files.length,
|
|
140
|
+
post,
|
|
141
|
+
honeypot
|
|
142
|
+
]);
|
|
143
|
+
const respond = useCallback(async ()=>{
|
|
144
|
+
if (sending) return;
|
|
145
|
+
setError('');
|
|
146
|
+
if (mode === 'approve' && name.trim().length < 2) return void setError('Type your name to approve the proof.');
|
|
147
|
+
if (mode === 'changes' && !comment.trim()) return void setError('Tell us what to change.');
|
|
148
|
+
setSending(true);
|
|
149
|
+
// One key per answer: a retry of the same press is answered from the first.
|
|
150
|
+
if (!attempt.current) attempt.current = newKey();
|
|
151
|
+
try {
|
|
152
|
+
var _payload_error;
|
|
153
|
+
const { response, payload } = await post('/api/commerce/quote-respond', _extends({
|
|
154
|
+
v: view.version,
|
|
155
|
+
action: mode === 'approve' ? 'approve' : 'changes'
|
|
156
|
+
}, mode === 'approve' ? {
|
|
157
|
+
name: name.trim()
|
|
158
|
+
} : {
|
|
159
|
+
comment: comment.trim(),
|
|
160
|
+
fileIds: files.map((file)=>file.id)
|
|
161
|
+
}, {
|
|
162
|
+
[VISITOR_UPLOAD_HONEYPOT_FIELD]: honeypot
|
|
163
|
+
}), {
|
|
164
|
+
'Idempotency-Key': attempt.current
|
|
165
|
+
});
|
|
166
|
+
attempt.current = '';
|
|
167
|
+
if (response.ok && payload['view']) {
|
|
168
|
+
setMode('idle');
|
|
169
|
+
setName('');
|
|
170
|
+
setComment('');
|
|
171
|
+
setFiles([]);
|
|
172
|
+
onView(payload['view']);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (payload['replaced']) return onReplaced();
|
|
176
|
+
if (payload['reason'] === 'updated' || payload['reason'] === 'paid') onReload();
|
|
177
|
+
setError(String((_payload_error = payload['error']) != null ? _payload_error : 'Your answer could not be sent. Try again.'));
|
|
178
|
+
} catch (unused) {
|
|
179
|
+
setError('Your answer could not be sent. Check your connection and try again.');
|
|
180
|
+
} finally{
|
|
181
|
+
setSending(false);
|
|
182
|
+
}
|
|
183
|
+
}, [
|
|
184
|
+
sending,
|
|
185
|
+
mode,
|
|
186
|
+
name,
|
|
187
|
+
comment,
|
|
188
|
+
files,
|
|
189
|
+
view.version,
|
|
190
|
+
honeypot,
|
|
191
|
+
post,
|
|
192
|
+
onView,
|
|
193
|
+
onReload,
|
|
194
|
+
onReplaced
|
|
195
|
+
]);
|
|
196
|
+
if (!view.proofs.length) return null;
|
|
197
|
+
const response = view.response;
|
|
198
|
+
return /*#__PURE__*/ _jsx(Paper, {
|
|
199
|
+
variant: "outlined",
|
|
200
|
+
sx: {
|
|
201
|
+
p: {
|
|
202
|
+
xs: 2,
|
|
203
|
+
sm: 3
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
207
|
+
spacing: 2,
|
|
208
|
+
children: [
|
|
209
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
210
|
+
spacing: 0.5,
|
|
211
|
+
children: [
|
|
212
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
213
|
+
variant: "h6",
|
|
214
|
+
component: "h2",
|
|
215
|
+
children: view.proofs.length === 1 ? 'Your proof' : 'Your proofs'
|
|
216
|
+
}),
|
|
217
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
218
|
+
variant: "body2",
|
|
219
|
+
color: "text.secondary",
|
|
220
|
+
children: view.status === 'unpaid' ? `Revision ${view.revision}. Look it over, then approve it or tell ${business} what to change.` : `Revision ${view.revision}.`
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
}),
|
|
224
|
+
/*#__PURE__*/ _jsx(Stack, {
|
|
225
|
+
spacing: 1.5,
|
|
226
|
+
children: view.proofs.map((proof)=>proof.inline ? /*#__PURE__*/ _jsxs(Stack, {
|
|
227
|
+
spacing: 0.5,
|
|
228
|
+
children: [
|
|
229
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
230
|
+
component: "img",
|
|
231
|
+
src: proofHref(proof.id),
|
|
232
|
+
alt: proof.name,
|
|
233
|
+
loading: "lazy",
|
|
234
|
+
sx: {
|
|
235
|
+
display: 'block',
|
|
236
|
+
maxWidth: 1,
|
|
237
|
+
height: 'auto',
|
|
238
|
+
borderRadius: 1,
|
|
239
|
+
border: 1,
|
|
240
|
+
borderColor: 'divider'
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
244
|
+
variant: "caption",
|
|
245
|
+
color: "text.secondary",
|
|
246
|
+
children: proof.name
|
|
247
|
+
})
|
|
248
|
+
]
|
|
249
|
+
}, proof.id) : /*#__PURE__*/ _jsx(Box, {
|
|
250
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
251
|
+
variant: "outlined",
|
|
252
|
+
size: "small",
|
|
253
|
+
href: proofHref(proof.id),
|
|
254
|
+
download: proof.name,
|
|
255
|
+
children: `Download ${proof.name} (${formatVisitorUploadBytes(proof.size)})`
|
|
256
|
+
})
|
|
257
|
+
}, proof.id))
|
|
258
|
+
}),
|
|
259
|
+
response ? response.action === 'approve' ? /*#__PURE__*/ _jsx(Alert, {
|
|
260
|
+
severity: "success",
|
|
261
|
+
children: `You approved this proof on ${dateLabel(response.atMs)}${response.name ? ` as ${response.name}` : ''}.`
|
|
262
|
+
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
263
|
+
severity: "info",
|
|
264
|
+
children: `You asked for changes on ${dateLabel(response.atMs)}. ${business.charAt(0).toUpperCase()}${business.slice(1)} will send you a new proof.`
|
|
265
|
+
}) : null,
|
|
266
|
+
view.respondable ? /*#__PURE__*/ _jsxs(Stack, {
|
|
267
|
+
spacing: 2,
|
|
268
|
+
children: [
|
|
269
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
270
|
+
component: "input",
|
|
271
|
+
type: "text",
|
|
272
|
+
name: VISITOR_UPLOAD_HONEYPOT_FIELD,
|
|
273
|
+
tabIndex: -1,
|
|
274
|
+
autoComplete: "off",
|
|
275
|
+
"aria-hidden": "true",
|
|
276
|
+
value: honeypot,
|
|
277
|
+
onChange: (event)=>setHoneypot(event.target.value),
|
|
278
|
+
sx: {
|
|
279
|
+
position: 'absolute',
|
|
280
|
+
left: '-100vw',
|
|
281
|
+
width: 0,
|
|
282
|
+
height: 0,
|
|
283
|
+
opacity: 0
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
mode === 'idle' ? /*#__PURE__*/ _jsxs(Stack, {
|
|
287
|
+
direction: {
|
|
288
|
+
xs: 'column',
|
|
289
|
+
sm: 'row'
|
|
290
|
+
},
|
|
291
|
+
spacing: 1,
|
|
292
|
+
children: [
|
|
293
|
+
(response == null ? void 0 : response.action) !== 'approve' ? /*#__PURE__*/ _jsx(Button, {
|
|
294
|
+
variant: "contained",
|
|
295
|
+
onClick: ()=>setMode('approve'),
|
|
296
|
+
children: 'Approve this proof'
|
|
297
|
+
}) : null,
|
|
298
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
299
|
+
variant: (response == null ? void 0 : response.action) === 'approve' ? 'outlined' : 'text',
|
|
300
|
+
onClick: ()=>setMode('changes'),
|
|
301
|
+
children: 'Request changes'
|
|
302
|
+
})
|
|
303
|
+
]
|
|
304
|
+
}) : mode === 'approve' ? /*#__PURE__*/ _jsxs(Stack, {
|
|
305
|
+
spacing: 1.5,
|
|
306
|
+
children: [
|
|
307
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
308
|
+
variant: "body2",
|
|
309
|
+
children: `Type your name to approve revision ${view.revision}. ${business.charAt(0).toUpperCase()}${business.slice(1)} keeps your name and the time as the record of your approval.`
|
|
310
|
+
}),
|
|
311
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
312
|
+
label: "Your name",
|
|
313
|
+
value: name,
|
|
314
|
+
autoComplete: "name",
|
|
315
|
+
onChange: (event)=>setName(event.target.value),
|
|
316
|
+
slotProps: {
|
|
317
|
+
htmlInput: {
|
|
318
|
+
maxLength: 120
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
323
|
+
direction: "row",
|
|
324
|
+
spacing: 1,
|
|
325
|
+
children: [
|
|
326
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
327
|
+
variant: "contained",
|
|
328
|
+
disabled: sending,
|
|
329
|
+
onClick: ()=>void respond(),
|
|
330
|
+
children: sending ? 'Approving…' : 'Approve'
|
|
331
|
+
}),
|
|
332
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
333
|
+
disabled: sending,
|
|
334
|
+
onClick: ()=>setMode('idle'),
|
|
335
|
+
children: 'Cancel'
|
|
336
|
+
})
|
|
337
|
+
]
|
|
338
|
+
})
|
|
339
|
+
]
|
|
340
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
341
|
+
spacing: 1.5,
|
|
342
|
+
children: [
|
|
343
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
344
|
+
label: "What should change?",
|
|
345
|
+
value: comment,
|
|
346
|
+
multiline: true,
|
|
347
|
+
minRows: 3,
|
|
348
|
+
required: true,
|
|
349
|
+
onChange: (event)=>setComment(event.target.value),
|
|
350
|
+
slotProps: {
|
|
351
|
+
htmlInput: {
|
|
352
|
+
maxLength: 5000
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}),
|
|
356
|
+
/*#__PURE__*/ _jsx("input", {
|
|
357
|
+
ref: picker,
|
|
358
|
+
type: "file",
|
|
359
|
+
multiple: true,
|
|
360
|
+
hidden: true,
|
|
361
|
+
accept: VISITOR_UPLOAD_ACCEPT,
|
|
362
|
+
"data-testid": "quote-artwork-picker",
|
|
363
|
+
onChange: (event)=>{
|
|
364
|
+
var _event_target_files;
|
|
365
|
+
const picked = Array.from((_event_target_files = event.target.files) != null ? _event_target_files : []);
|
|
366
|
+
event.target.value = '';
|
|
367
|
+
if (picked.length) void attach(picked);
|
|
368
|
+
}
|
|
369
|
+
}),
|
|
370
|
+
files.length ? /*#__PURE__*/ _jsx(Stack, {
|
|
371
|
+
direction: "row",
|
|
372
|
+
spacing: 1,
|
|
373
|
+
useFlexGap: true,
|
|
374
|
+
sx: {
|
|
375
|
+
flexWrap: 'wrap'
|
|
376
|
+
},
|
|
377
|
+
children: files.map((file)=>/*#__PURE__*/ _jsx(Chip, {
|
|
378
|
+
label: `${file.name} · ${formatVisitorUploadBytes(file.size)}`,
|
|
379
|
+
onDelete: sending ? undefined : ()=>setFiles((prev)=>prev.filter((entry)=>entry.id !== file.id))
|
|
380
|
+
}, file.id))
|
|
381
|
+
}) : null,
|
|
382
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
383
|
+
children: [
|
|
384
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
385
|
+
size: "small",
|
|
386
|
+
variant: "outlined",
|
|
387
|
+
disabled: sending || Boolean(uploading) || files.length >= VISITOR_UPLOAD_MAX_FILES,
|
|
388
|
+
onClick: ()=>{
|
|
389
|
+
var _picker_current;
|
|
390
|
+
return (_picker_current = picker.current) == null ? void 0 : _picker_current.click();
|
|
391
|
+
},
|
|
392
|
+
children: uploading ? `Sending ${uploading.done + 1} of ${uploading.total}…` : 'Attach artwork'
|
|
393
|
+
}),
|
|
394
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
395
|
+
variant: "caption",
|
|
396
|
+
color: "text.secondary",
|
|
397
|
+
component: "p",
|
|
398
|
+
sx: {
|
|
399
|
+
mt: 0.5
|
|
400
|
+
},
|
|
401
|
+
children: `Optional. ${describeVisitorUploadTypes()}, up to ${VISITOR_UPLOAD_MAX_FILES} files of ${formatVisitorUploadBytes(VISITOR_UPLOAD_MAX_BYTES)} each.`
|
|
402
|
+
})
|
|
403
|
+
]
|
|
404
|
+
}),
|
|
405
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
406
|
+
direction: "row",
|
|
407
|
+
spacing: 1,
|
|
408
|
+
children: [
|
|
409
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
410
|
+
variant: "contained",
|
|
411
|
+
disabled: sending || Boolean(uploading),
|
|
412
|
+
onClick: ()=>void respond(),
|
|
413
|
+
children: sending ? 'Sending…' : 'Send changes'
|
|
414
|
+
}),
|
|
415
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
416
|
+
disabled: sending,
|
|
417
|
+
onClick: ()=>setMode('idle'),
|
|
418
|
+
children: 'Cancel'
|
|
419
|
+
})
|
|
420
|
+
]
|
|
421
|
+
})
|
|
422
|
+
]
|
|
423
|
+
}),
|
|
424
|
+
error ? /*#__PURE__*/ _jsx(Alert, {
|
|
425
|
+
severity: "error",
|
|
426
|
+
children: error
|
|
427
|
+
}) : null
|
|
428
|
+
]
|
|
429
|
+
}) : null
|
|
430
|
+
]
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
export default QuoteProofPanel;
|
|
435
|
+
|
|
436
|
+
//# sourceMappingURL=quote-proof-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/quote-proof-panel.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n checkVisitorUploadSelection,\n describeVisitorUploadTypes,\n formatVisitorUploadBytes,\n VISITOR_UPLOAD_ACCEPT,\n VISITOR_UPLOAD_HONEYPOT_FIELD,\n VISITOR_UPLOAD_MAX_BYTES,\n VISITOR_UPLOAD_MAX_FILES,\n type VisitorUploadFileView,\n} from '@aglyn/aglyn/app-utils/visitor-upload'\nimport { sendVisitorUpload } from '@aglyn/aglyn/app-utils/visitor-upload-client'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Chip from '@mui/material/Chip'\nimport Paper from '@mui/material/Paper'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { useCallback, useRef, useState, type ChangeEvent } from 'react'\nimport type { QuotePageView } from '../model'\n\n/** The page's own fetch: the site's API, with the site's preview rules. */\ntype SiteFetch = (input: string, init?: RequestInit) => Promise<Response>\n\nexport interface QuoteProofPanelProps {\n view: QuotePageView\n hostId: string\n quoteId: string\n token: string\n siteFetch: SiteFetch\n /** The quote as the server answered after a response, or after it changed. */\n onView: (view: QuotePageView) => void\n /** Reload the quote: it changed since the page loaded. */\n onReload: () => void\n /** The link was replaced. */\n onReplaced: () => void\n}\n\nfunction dateLabel(atMs: number): string {\n return new Date(atMs).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })\n}\n\nconst newKey = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`\n\n/**\n * The quote's proofs on the customer's page (AGL-4096): each image shown,\n * every other file a download, and while the quote is open for an answer,\n * \"Approve this proof\" (under a typed name) and \"Request changes\" (a comment\n * and any artwork). The latest answer is the one that counts, so a customer\n * who asked for changes can still approve, and the panel says what they last\n * said. A paid quote shows its proofs and nothing to press.\n */\nexport function QuoteProofPanel(props: QuoteProofPanelProps) {\n const { view, hostId, quoteId, token, siteFetch, onView, onReload, onReplaced } = props\n const [mode, setMode] = useState<'idle' | 'approve' | 'changes'>('idle')\n const [name, setName] = useState('')\n const [comment, setComment] = useState('')\n const [files, setFiles] = useState<VisitorUploadFileView[]>([])\n const [uploading, setUploading] = useState<{ done: number; total: number } | null>(null)\n const [sending, setSending] = useState(false)\n const [error, setError] = useState('')\n // Whatever a bot types into the hidden field, sent as typed.\n const [honeypot, setHoneypot] = useState('')\n const picker = useRef<HTMLInputElement>(null)\n const attempt = useRef('')\n\n const link = `hostId=${encodeURIComponent(hostId)}&q=${encodeURIComponent(quoteId)}&t=${encodeURIComponent(token)}`\n const proofHref = (id: string) => `/api/commerce/quote-proof?${link}&file=${encodeURIComponent(id)}`\n const business = view.storeName || 'the business'\n\n const post = useCallback(\n async (path: string, body: Record<string, unknown>, headers: Record<string, string> = {}) => {\n const response = await siteFetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify({ hostId, q: quoteId, t: token, ...body }),\n })\n return { response, payload: (await response.json().catch(() => ({}))) as Record<string, any> }\n },\n [siteFetch, hostId, quoteId, token],\n )\n\n const attach = useCallback(\n async (picked: File[]) => {\n setError('')\n const problem = checkVisitorUploadSelection(picked, files.length)\n if (problem) return void setError(problem)\n setUploading({ done: 0, total: picked.length })\n try {\n for (const [index, file] of picked.entries()) {\n const sent = await sendVisitorUpload({\n file,\n requestTicket: async (request) => {\n const { response, payload } = await post('/api/commerce/quote-upload', {\n action: 'ticket',\n file: request,\n [VISITOR_UPLOAD_HONEYPOT_FIELD]: honeypot,\n })\n return response.ok && payload['ticket']\n ? (payload as { ticket: string; upload: { url: string; fields: Record<string, string> } })\n : { error: String(payload['error'] ?? 'The file could not be sent. Try again.') }\n },\n finalize: async (ticket) => {\n const { response, payload } = await post('/api/commerce/quote-upload', { action: 'finalize', ticket })\n return response.ok && payload['file']\n ? { file: payload['file'] as VisitorUploadFileView }\n : { error: String(payload['error'] ?? 'The file could not be sent. Try again.') }\n },\n })\n if ('error' in sent) {\n setError(sent.error)\n break\n }\n setFiles((prev) => [...prev, sent.file])\n setUploading({ done: index + 1, total: picked.length })\n }\n } finally {\n setUploading(null)\n }\n },\n [files.length, post, honeypot],\n )\n\n const respond = useCallback(async () => {\n if (sending) return\n setError('')\n if (mode === 'approve' && name.trim().length < 2) return void setError('Type your name to approve the proof.')\n if (mode === 'changes' && !comment.trim()) return void setError('Tell us what to change.')\n setSending(true)\n // One key per answer: a retry of the same press is answered from the first.\n if (!attempt.current) attempt.current = newKey()\n try {\n const { response, payload } = await post(\n '/api/commerce/quote-respond',\n {\n v: view.version,\n action: mode === 'approve' ? 'approve' : 'changes',\n ...(mode === 'approve' ? { name: name.trim() } : { comment: comment.trim(), fileIds: files.map((file) => file.id) }),\n [VISITOR_UPLOAD_HONEYPOT_FIELD]: honeypot,\n },\n { 'Idempotency-Key': attempt.current },\n )\n attempt.current = ''\n if (response.ok && payload['view']) {\n setMode('idle')\n setName('')\n setComment('')\n setFiles([])\n onView(payload['view'] as QuotePageView)\n return\n }\n if (payload['replaced']) return onReplaced()\n if (payload['reason'] === 'updated' || payload['reason'] === 'paid') onReload()\n setError(String(payload['error'] ?? 'Your answer could not be sent. Try again.'))\n } catch {\n setError('Your answer could not be sent. Check your connection and try again.')\n } finally {\n setSending(false)\n }\n }, [sending, mode, name, comment, files, view.version, honeypot, post, onView, onReload, onReplaced])\n\n if (!view.proofs.length) return null\n const response = view.response\n\n return (\n <Paper variant=\"outlined\" sx={{ p: { xs: 2, sm: 3 } }}>\n <Stack spacing={2}>\n <Stack spacing={0.5}>\n <Typography variant=\"h6\" component=\"h2\">\n {view.proofs.length === 1 ? 'Your proof' : 'Your proofs'}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {view.status === 'unpaid'\n ? `Revision ${view.revision}. Look it over, then approve it or tell ${business} what to change.`\n : `Revision ${view.revision}.`}\n </Typography>\n </Stack>\n\n <Stack spacing={1.5}>\n {view.proofs.map((proof) =>\n proof.inline ? (\n <Stack key={proof.id} spacing={0.5}>\n <Box\n component=\"img\"\n src={proofHref(proof.id)}\n alt={proof.name}\n loading=\"lazy\"\n sx={{ display: 'block', maxWidth: 1, height: 'auto', borderRadius: 1, border: 1, borderColor: 'divider' }}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {proof.name}\n </Typography>\n </Stack>\n ) : (\n <Box key={proof.id}>\n <Button variant=\"outlined\" size=\"small\" href={proofHref(proof.id)} download={proof.name}>\n {`Download ${proof.name} (${formatVisitorUploadBytes(proof.size)})`}\n </Button>\n </Box>\n ),\n )}\n </Stack>\n\n {response ? (\n response.action === 'approve' ? (\n <Alert severity=\"success\">\n {`You approved this proof on ${dateLabel(response.atMs)}${response.name ? ` as ${response.name}` : ''}.`}\n </Alert>\n ) : (\n <Alert severity=\"info\">\n {`You asked for changes on ${dateLabel(response.atMs)}. ${business.charAt(0).toUpperCase()}${business.slice(1)} will send you a new proof.`}\n </Alert>\n )\n ) : null}\n\n {view.respondable ? (\n <Stack spacing={2}>\n {/* Honeypot: people never see or fill this. */}\n <Box\n component=\"input\"\n type=\"text\"\n name={VISITOR_UPLOAD_HONEYPOT_FIELD}\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n value={honeypot}\n onChange={(event: ChangeEvent<HTMLInputElement>) => setHoneypot(event.target.value)}\n sx={{ position: 'absolute', left: '-100vw', width: 0, height: 0, opacity: 0 }}\n />\n {mode === 'idle' ? (\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n {response?.action !== 'approve' ? (\n <Button variant=\"contained\" onClick={() => setMode('approve')}>\n {'Approve this proof'}\n </Button>\n ) : null}\n <Button variant={response?.action === 'approve' ? 'outlined' : 'text'} onClick={() => setMode('changes')}>\n {'Request changes'}\n </Button>\n </Stack>\n ) : mode === 'approve' ? (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\">\n {`Type your name to approve revision ${view.revision}. ${business.charAt(0).toUpperCase()}${business.slice(1)} keeps your name and the time as the record of your approval.`}\n </Typography>\n <TextField\n label=\"Your name\"\n value={name}\n autoComplete=\"name\"\n onChange={(event) => setName(event.target.value)}\n slotProps={{ htmlInput: { maxLength: 120 } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" disabled={sending} onClick={() => void respond()}>\n {sending ? 'Approving…' : 'Approve'}\n </Button>\n <Button disabled={sending} onClick={() => setMode('idle')}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n ) : (\n <Stack spacing={1.5}>\n <TextField\n label=\"What should change?\"\n value={comment}\n multiline\n minRows={3}\n required\n onChange={(event) => setComment(event.target.value)}\n slotProps={{ htmlInput: { maxLength: 5000 } }}\n />\n <input\n ref={picker}\n type=\"file\"\n multiple\n hidden\n accept={VISITOR_UPLOAD_ACCEPT}\n data-testid=\"quote-artwork-picker\"\n onChange={(event) => {\n const picked = Array.from(event.target.files ?? [])\n event.target.value = ''\n if (picked.length) void attach(picked)\n }}\n />\n {files.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {files.map((file) => (\n <Chip\n key={file.id}\n label={`${file.name} · ${formatVisitorUploadBytes(file.size)}`}\n onDelete={sending ? undefined : () => setFiles((prev) => prev.filter((entry) => entry.id !== file.id))}\n />\n ))}\n </Stack>\n ) : null}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={sending || Boolean(uploading) || files.length >= VISITOR_UPLOAD_MAX_FILES}\n onClick={() => picker.current?.click()}\n >\n {uploading ? `Sending ${uploading.done + 1} of ${uploading.total}…` : 'Attach artwork'}\n </Button>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{ mt: 0.5 }}>\n {`Optional. ${describeVisitorUploadTypes()}, up to ${VISITOR_UPLOAD_MAX_FILES} files of ${formatVisitorUploadBytes(VISITOR_UPLOAD_MAX_BYTES)} each.`}\n </Typography>\n </Box>\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" disabled={sending || Boolean(uploading)} onClick={() => void respond()}>\n {sending ? 'Sending…' : 'Send changes'}\n </Button>\n <Button disabled={sending} onClick={() => setMode('idle')}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )}\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </Stack>\n ) : null}\n </Stack>\n </Paper>\n )\n}\n\nexport default QuoteProofPanel\n"],"names":["checkVisitorUploadSelection","describeVisitorUploadTypes","formatVisitorUploadBytes","VISITOR_UPLOAD_ACCEPT","VISITOR_UPLOAD_HONEYPOT_FIELD","VISITOR_UPLOAD_MAX_BYTES","VISITOR_UPLOAD_MAX_FILES","sendVisitorUpload","Alert","Box","Button","Chip","Paper","Stack","TextField","Typography","useCallback","useRef","useState","dateLabel","atMs","Date","toLocaleDateString","undefined","month","day","year","newKey","globalThis","crypto","randomUUID","now","Math","random","toString","slice","QuoteProofPanel","props","view","hostId","quoteId","token","siteFetch","onView","onReload","onReplaced","mode","setMode","name","setName","comment","setComment","files","setFiles","uploading","setUploading","sending","setSending","error","setError","honeypot","setHoneypot","picker","attempt","link","encodeURIComponent","proofHref","id","business","storeName","post","path","body","headers","response","method","JSON","stringify","q","t","payload","json","catch","attach","picked","problem","length","done","total","index","file","entries","sent","requestTicket","request","action","ok","String","finalize","ticket","prev","respond","trim","current","v","version","fileIds","map","proofs","variant","sx","p","xs","sm","spacing","component","color","status","revision","proof","inline","src","alt","loading","display","maxWidth","height","borderRadius","border","borderColor","size","href","download","severity","charAt","toUpperCase","respondable","type","tabIndex","autoComplete","aria-hidden","value","onChange","event","target","position","left","width","opacity","direction","onClick","label","slotProps","htmlInput","maxLength","disabled","multiline","minRows","required","input","ref","multiple","hidden","accept","data-testid","Array","from","useFlexGap","flexWrap","onDelete","filter","entry","Boolean","click","mt"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
*/
|
|
17
|
+
import { type HostOrder, type OrderTimelineEvent } from './commerce-orders';
|
|
18
|
+
import { type QuoteCheckoutCharge, type QuotePageView, type QuotePayRefusal } from './commerce-quote-pay';
|
|
19
|
+
import { type OrderInvoiceSendRecord, type QuoteEmailThread, type QuoteTaxBasis } from './commerce-quotes';
|
|
20
|
+
import type { ResolvedShippingRate } from './commerce-shipping';
|
|
21
|
+
/** Which page a pay link opens for an order: a quote, an invoice, or none. */
|
|
22
|
+
export type OrderPayLinkKind = 'quote' | 'invoice';
|
|
23
|
+
/** The most invoice sends an order keeps on record; the oldest go first. */
|
|
24
|
+
export declare const INVOICE_MAX_SENDS = 50;
|
|
25
|
+
/**
|
|
26
|
+
* Which page the lasting pay link opens for an order, or null when it opens
|
|
27
|
+
* none: a quote (with its quote section), or a draft order as an invoice.
|
|
28
|
+
*/
|
|
29
|
+
export declare function orderPayLinkKind(order: Pick<HostOrder, 'channel'> & Partial<Pick<HostOrder, 'quote'>> | null | undefined): OrderPayLinkKind | null;
|
|
30
|
+
/** `Invoice #1042`: the order's own number, which a paid quote keeps too. */
|
|
31
|
+
export declare function formatInvoiceNumber(order: Pick<HostOrder, 'number'>, orderId?: string): string;
|
|
32
|
+
/** The order is paid (and not refunded or canceled): its invoice is the record. */
|
|
33
|
+
export declare function invoiceIsPaid(order: Pick<HostOrder, 'status'> | null | undefined): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* When the order was paid: a quote's stamp, else the timeline's `paid`
|
|
36
|
+
* event, else (an order created paid, as checkout's are) when it was made.
|
|
37
|
+
* Null for an order not paid.
|
|
38
|
+
*/
|
|
39
|
+
export declare function orderPaidAtMs(order: Pick<HostOrder, 'status' | 'quote' | 'timeline'> & {
|
|
40
|
+
createdAtMs?: number;
|
|
41
|
+
} | null | undefined): number | null;
|
|
42
|
+
/** What an invoice's Checkout Session charges, with the rates a draft lets the buyer pick from. */
|
|
43
|
+
export interface InvoiceCheckoutCharge extends QuoteCheckoutCharge {
|
|
44
|
+
/** The draft's shipping rates, picked at payment; empty when shipping is fixed or there is none. */
|
|
45
|
+
shippingOptions: ResolvedShippingRate[];
|
|
46
|
+
/** Where those rates ship; empty with none. */
|
|
47
|
+
shippingCountries: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The tax basis an order's stored figures give its invoice: Stripe Tax at
|
|
51
|
+
* payment where the order was composed under it, its frozen tax as a fixed
|
|
52
|
+
* figure (charged as the rate that yields it on the taxed line, the way a tax
|
|
53
|
+
* service's quote is), else none.
|
|
54
|
+
*/
|
|
55
|
+
export declare function invoiceTaxBasis(order: Pick<HostOrder, 'taxMode' | 'totals'>): QuoteTaxBasis;
|
|
56
|
+
/**
|
|
57
|
+
* What an invoice's pay link charges, from the stored order alone. A quote is
|
|
58
|
+
* charged as slice 2 charges it; any other order from its lines, its
|
|
59
|
+
* discount, its tax ({@link invoiceTaxBasis}) and, for a draft, the shipping
|
|
60
|
+
* rates it offered when it was made (Stripe asks for the address and the
|
|
61
|
+
* buyer picks one, as on the draft's first link).
|
|
62
|
+
*/
|
|
63
|
+
export declare function invoiceCheckoutCharge(order: Pick<HostOrder, 'number' | 'lineItems' | 'quote' | 'totals' | 'taxMode'> & Partial<Pick<HostOrder, 'draftShipping'>>, orderId: string): InvoiceCheckoutCharge;
|
|
64
|
+
/**
|
|
65
|
+
* Why an order cannot be paid through its pay link now: a quote's reasons,
|
|
66
|
+
* and two of an invoice's own. `not-payable` is an order the link does not
|
|
67
|
+
* open (a register sale still open, say); `shipping-unknown` is a draft made
|
|
68
|
+
* before drafts kept their shipping rates, whose shipping cannot be charged.
|
|
69
|
+
*/
|
|
70
|
+
export type InvoicePayRefusal = QuotePayRefusal | 'not-payable' | 'shipping-unknown';
|
|
71
|
+
export declare function invoicePayRefusal(order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'quote' | 'totals' | 'taxMode' | 'channel'> & Partial<Pick<HostOrder, 'draftShipping'>>, orderId: string): InvoicePayRefusal | null;
|
|
72
|
+
/** What the customer is told for a refusal, in the page's words: "quote" or "invoice". */
|
|
73
|
+
export declare function invoicePayRefusalMessage(reason: InvoicePayRefusal, storeName: string, kind: OrderPayLinkKind): string;
|
|
74
|
+
/**
|
|
75
|
+
* The pay link's page for an order that is not a quote: "Invoice #N", its
|
|
76
|
+
* lines and totals, the customer's addresses, and Pay while it is unpaid. No
|
|
77
|
+
* proofs, no approval, no notes or terms (an order carries none). Allow-listed
|
|
78
|
+
* as the quote's view is: the order's email, phone, Stripe ids and timeline
|
|
79
|
+
* never leave the server.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildInvoicePageView(input: {
|
|
82
|
+
order: Pick<HostOrder, 'number' | 'status' | 'channel' | 'lineItems' | 'totals' | 'quote' | 'timeline' | 'taxMode' | 'customerName' | 'billingAddress' | 'shippingAddress'> & Partial<Pick<HostOrder, 'draftShipping'>> & {
|
|
83
|
+
createdAtMs?: number;
|
|
84
|
+
};
|
|
85
|
+
orderId: string;
|
|
86
|
+
storeName: string;
|
|
87
|
+
currency: string;
|
|
88
|
+
/** The order's status page, once paid. */
|
|
89
|
+
receiptUrl?: string | null;
|
|
90
|
+
}): QuotePageView;
|
|
91
|
+
/** The order's invoice sends on record, oldest first. */
|
|
92
|
+
export declare function invoiceSends(order: Pick<HostOrder, 'invoiceSends'> | null | undefined): OrderInvoiceSendRecord[];
|
|
93
|
+
/** The last time the order's invoice was emailed, or null. */
|
|
94
|
+
export declare function lastInvoiceSend(order: Pick<HostOrder, 'invoiceSends'> | null | undefined): OrderInvoiceSendRecord | null;
|
|
95
|
+
/** Where the invoice goes: what was typed, else the order's customer, else a quote's. */
|
|
96
|
+
export declare function invoiceSendRecipient(order: Pick<HostOrder, 'customerEmail' | 'quote'> | null | undefined, typed: string | null | undefined): string;
|
|
97
|
+
/**
|
|
98
|
+
* The conversation an invoice replies into: a quote's own (its emails, and a
|
|
99
|
+
* request's confirmation before them), else the order's, which the first
|
|
100
|
+
* invoice starts.
|
|
101
|
+
*/
|
|
102
|
+
export declare function invoiceEmailThread(order: Pick<HostOrder, 'quote' | 'emailThread'> | null | undefined): QuoteEmailThread | null;
|
|
103
|
+
/**
|
|
104
|
+
* The subject an invoice defaults to: the conversation's, so it stays one
|
|
105
|
+
* conversation in the customer's mailbox, else `Invoice #1042 from Acme Signs`.
|
|
106
|
+
*/
|
|
107
|
+
export declare function invoiceEmailDefaultSubject(order: Pick<HostOrder, 'number' | 'quote' | 'emailThread'>, businessName: string, orderId?: string): string;
|
|
108
|
+
/** Why an invoice cannot be emailed now, and what the console says. */
|
|
109
|
+
export type InvoiceSendRefusal = 'unknown' | 'closed' | 'empty' | 'not-payable' | 'approval-required' | 'tax-undecided' | 'nothing-to-pay' | 'shipping-unknown' | 'no-address' | 'bad-address';
|
|
110
|
+
/**
|
|
111
|
+
* Why the order's invoice cannot be emailed to `to` now, if it cannot:
|
|
112
|
+
* a canceled or refunded order, one with nothing on it, an unpaid order its
|
|
113
|
+
* pay link cannot take payment for (and why), and the address.
|
|
114
|
+
*/
|
|
115
|
+
export declare function invoiceSendRefusal(order: (Pick<HostOrder, 'number' | 'status' | 'channel' | 'lineItems' | 'totals' | 'quote' | 'taxMode'> & Partial<Pick<HostOrder, 'draftShipping'>>) | null | undefined, orderId: string, to: string): InvoiceSendRefusal | null;
|
|
116
|
+
/** What the console says for a refused invoice send. */
|
|
117
|
+
export declare function invoiceSendRefusalMessage(reason: InvoiceSendRefusal): string;
|
|
118
|
+
/** The HTTP status a refused send answers with. */
|
|
119
|
+
export declare function invoiceSendRefusalStatus(reason: InvoiceSendRefusal): number;
|
|
120
|
+
/**
|
|
121
|
+
* What an invoice email changes on the order, as a merge: the send on
|
|
122
|
+
* record, a line on the timeline, and the conversation moved on, on the
|
|
123
|
+
* quote for a quote and on the order otherwise.
|
|
124
|
+
*/
|
|
125
|
+
export declare function invoiceSendPatch(order: Pick<HostOrder, 'timeline' | 'invoiceSends' | 'quote'>, send: OrderInvoiceSendRecord, thread: QuoteEmailThread): {
|
|
126
|
+
timeline: OrderTimelineEvent[];
|
|
127
|
+
invoiceSends: OrderInvoiceSendRecord[];
|
|
128
|
+
emailThread?: QuoteEmailThread;
|
|
129
|
+
quote?: {
|
|
130
|
+
emailThread: QuoteEmailThread;
|
|
131
|
+
};
|
|
132
|
+
};
|