@aglyn/plugins-commerce 1.0.0-beta.290 → 1.0.0-beta.292
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/package.json +13 -13
- package/src/lib/components/cart-fulfillment.d.ts +3 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -1
- package/src/lib/components/cart.js +14 -4
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +2 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- 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/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 +9 -0
- package/src/lib/components/console/order-invoice.component.js +27 -2
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +1 -1
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
- package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
- package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +31 -5
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
- package/src/lib/components/console/product-register-fields.component.js +8 -197
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +6 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +102 -4
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
- package/src/lib/components/console/quote-pay-link.component.js +29 -3
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
- package/src/lib/components/console/quote-proofs.component.d.ts +11 -0
- package/src/lib/components/console/quote-proofs.component.js +68 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -1
- package/src/lib/components/console/quote-send.component.d.ts +42 -0
- package/src/lib/components/console/quote-send.component.js +304 -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-add-ons.d.ts +28 -0
- package/src/lib/components/product-add-ons.js +136 -0
- package/src/lib/components/product-add-ons.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +16 -0
- package/src/lib/components/product-detail.js +112 -19
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +8 -2
- package/src/lib/components/product-grid.js +6 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/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 +39 -20
- package/src/lib/components/quote-page-body.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +15 -3
- package/src/lib/model/commerce-cart.js +10 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-invoices.d.ts +139 -0
- package/src/lib/model/commerce-invoices.js +343 -0
- package/src/lib/model/commerce-invoices.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +35 -5
- package/src/lib/model/commerce-orders.js +3 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
- package/src/lib/model/commerce-pos-kiosk.js +3 -2
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
- package/src/lib/model/commerce-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 +81 -0
- package/src/lib/model/commerce-quote-email.js +170 -0
- package/src/lib/model/commerce-quote-email.js.map +1 -0
- package/src/lib/model/commerce-quote-pay.d.ts +49 -6
- package/src/lib/model/commerce-quote-pay.js +69 -36
- package/src/lib/model/commerce-quote-pay.js.map +1 -1
- 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 +81 -0
- package/src/lib/model/commerce-quotes.js +1 -1
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/commerce.d.ts +18 -0
- package/src/lib/model/commerce.js +26 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +3 -0
- package/src/lib/model/index.js +3 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/product-modifiers.d.ts +74 -4
- package/src/lib/model/product-modifiers.js +131 -10
- package/src/lib/model/product-modifiers.js.map +1 -1
- package/src/lib/plugin.js +19 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +164 -98
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +14 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.d.ts +5 -0
- package/src/lib/server/cart.js +41 -4
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +2 -0
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +3 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +28 -3
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/commerce-pdf.d.ts +5 -1
- package/src/lib/server/commerce-pdf.js +37 -10
- package/src/lib/server/commerce-pdf.js.map +1 -1
- 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/local-fulfillment.js +5 -1
- package/src/lib/server/local-fulfillment.js.map +1 -1
- 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-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/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-offline-sync.js +1 -1
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-order.js +1 -1
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/product.d.ts +26 -0
- package/src/lib/server/product.js +18 -0
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.js +5 -2
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/quote-email.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 +8 -3
- package/src/lib/server/quote-pay.js +105 -43
- package/src/lib/server/quote-pay.js.map +1 -1
- package/src/lib/server/quote.d.ts +11 -0
- package/src/lib/server/quote.js +309 -12
- package/src/lib/server/quote.js.map +1 -1
- 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 +5 -0
- 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/add-to-cart.d.ts +8 -0
- package/src/lib/utils/add-to-cart.js +10 -2
- package/src/lib/utils/add-to-cart.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +5 -0
- package/src/lib/utils/commerce-document-pdf.js +6 -1
- package/src/lib/utils/commerce-document-pdf.js.map +1 -1
- 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 +1 -1
- package/src/lib/utils/order-invoice.js +14 -6
- package/src/lib/utils/order-invoice.js.map +1 -1
- package/src/lib/utils/quote-email-content.d.ts +63 -0
- package/src/lib/utils/quote-email-content.js +157 -0
- package/src/lib/utils/quote-email-content.js.map +1 -0
|
@@ -22,7 +22,7 @@ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
|
22
22
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
23
23
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
24
24
|
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField, Typography } from "@mui/material";
|
|
25
|
-
import { useCallback, useState } from "react";
|
|
25
|
+
import { useCallback, useEffect, useState } from "react";
|
|
26
26
|
const SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send');
|
|
27
27
|
/**
|
|
28
28
|
* A quote's pay link (AGL-4094): the address the business sends the
|
|
@@ -30,14 +30,24 @@ const SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send');
|
|
|
30
30
|
* button. "Copy pay link" asks the server for it (it is signed there) and puts
|
|
31
31
|
* it on the clipboard and on screen; "Replace link" makes a new one and the
|
|
32
32
|
* one sent before stops opening the quote.
|
|
33
|
+
*
|
|
34
|
+
* The link shown is kept with the version it was made at, and drops as soon
|
|
35
|
+
* as the quote's stored version (`quote.linkVersion`) moves past it: a
|
|
36
|
+
* revision's first email and Replace link (here or anywhere else) both move
|
|
37
|
+
* it, and the link from before then opens only "This quote was updated". A
|
|
38
|
+
* link already on screen is fetched again at the new version; Copy pay link
|
|
39
|
+
* never copies one older than the quote's.
|
|
33
40
|
*/ export function QuotePayLinkSection(props) {
|
|
34
41
|
const { hostId, quoteId, quote, canSend, edited } = props;
|
|
35
42
|
const { data: user } = useUser();
|
|
36
43
|
const { enqueueSnackbar } = useSnackbar();
|
|
37
|
-
const [
|
|
44
|
+
const [signed, setSigned] = useState(null);
|
|
38
45
|
const [busy, setBusy] = useState(false);
|
|
39
46
|
const [confirmingReplace, setConfirmingReplace] = useState(false);
|
|
40
47
|
const unpaid = CommerceModel.isUnpaidQuote(quote);
|
|
48
|
+
const version = CommerceModel.quoteLinkVersion(quote.quote);
|
|
49
|
+
// A link the server made at a version the stored quote has not caught up to yet is the newest one.
|
|
50
|
+
const url = signed && signed.version >= version ? signed.url : '';
|
|
41
51
|
const ask = useCallback(async (action)=>{
|
|
42
52
|
setBusy(true);
|
|
43
53
|
try {
|
|
@@ -61,7 +71,11 @@ const SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send');
|
|
|
61
71
|
});
|
|
62
72
|
return null;
|
|
63
73
|
}
|
|
64
|
-
|
|
74
|
+
const made = Math.floor(Number(payload.linkVersion));
|
|
75
|
+
setSigned({
|
|
76
|
+
url: String(payload.url),
|
|
77
|
+
version: Number.isFinite(made) && made >= 1 ? made : version
|
|
78
|
+
});
|
|
65
79
|
return String(payload.url);
|
|
66
80
|
} finally{
|
|
67
81
|
setBusy(false);
|
|
@@ -70,8 +84,20 @@ const SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send');
|
|
|
70
84
|
user,
|
|
71
85
|
hostId,
|
|
72
86
|
quoteId,
|
|
87
|
+
version,
|
|
73
88
|
enqueueSnackbar
|
|
74
89
|
]);
|
|
90
|
+
// The quote's link moved on (a revision was emailed, or the link replaced):
|
|
91
|
+
// the one on screen opens nothing now, so fetch the current one in its place.
|
|
92
|
+
const stale = signed !== null && signed.version < version;
|
|
93
|
+
useEffect(()=>{
|
|
94
|
+
if (stale) void ask('pay-link');
|
|
95
|
+
// Once per version the quote moves to; `ask` changing does not ask again.
|
|
96
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
97
|
+
}, [
|
|
98
|
+
stale,
|
|
99
|
+
version
|
|
100
|
+
]);
|
|
75
101
|
const copy = useCallback(async (link, done)=>{
|
|
76
102
|
try {
|
|
77
103
|
await navigator.clipboard.writeText(link);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/quote-pay-link.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { commerceSitePermissionRefusal } from '../../model/plugin-permissions'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useState } from 'react'\n\nconst SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send')\n\nexport interface QuotePayLinkSectionProps {\n hostId: string\n quoteId: string\n quote: Pick<CommerceModel.HostOrder, 'status' | 'channel' | 'number'>\n /** Holds \"Send quotes and invoices to customers\" (`commerce.quotes.send`). */\n canSend: boolean\n /** The editor holds changes not saved yet, which the link does not show. */\n edited: boolean\n}\n\n/**\n * A quote's pay link (AGL-4094): the address the business sends the\n * customer, which opens the quote on the business's own site with a Pay\n * button. \"Copy pay link\" asks the server for it (it is signed there) and puts\n * it on the clipboard and on screen; \"Replace link\" makes a new one and the\n * one sent before stops opening the quote.\n */\nexport function QuotePayLinkSection(props: QuotePayLinkSectionProps) {\n const { hostId, quoteId, quote, canSend, edited } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [url, setUrl] = useState('')\n const [busy, setBusy] = useState(false)\n const [confirmingReplace, setConfirmingReplace] = useState(false)\n const unpaid = CommerceModel.isUnpaidQuote(quote)\n\n const ask = useCallback(\n async (action: 'pay-link' | 'replace-link'): Promise<string | null> => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action, hostId, quoteId }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.url) {\n enqueueSnackbar(payload?.error ?? 'The pay link could not be made', { variant: 'error', allowDuplicate: true })\n return null\n }\n setUrl(String(payload.url))\n return String(payload.url)\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, quoteId, enqueueSnackbar],\n )\n\n const copy = useCallback(\n async (link: string, done: string) => {\n try {\n await navigator.clipboard.writeText(link)\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch {\n // The link is on screen to copy by hand when the clipboard is refused.\n enqueueSnackbar('Copy the link below to send it', { variant: 'info', persist: false })\n }\n },\n [enqueueSnackbar],\n )\n\n const copyLink = useCallback(async () => {\n const link = url || (await ask('pay-link'))\n if (link) await copy(link, 'Pay link copied')\n }, [url, ask, copy])\n\n const replaceLink = useCallback(async () => {\n const link = await ask('replace-link')\n setConfirmingReplace(false)\n if (link) await copy(link, 'New pay link copied. The earlier link no longer opens the quote.')\n }, [ask, copy])\n\n return (\n <Stack spacing={1.5}>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Pay link'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {unpaid\n ? 'Send this link to the customer. It opens the quote on your site, where they can pay it.'\n : 'The link opens the paid quote and the customer’s order.'}\n </Typography>\n {edited && unpaid ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Save your changes first: the link opens the quote as it was last saved.'}\n </Typography>\n ) : null}\n </Stack>\n {url ? (\n <TextField\n label=\"Pay link\"\n value={url}\n size=\"small\"\n fullWidth\n slotProps={{ htmlInput: { readOnly: true }, inputLabel: { shrink: true } }}\n onFocus={(event) => event.target.select()}\n />\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n <BlockedControl blocked={!canSend} reason={SEND_REFUSAL}>\n <Button variant=\"outlined\" size=\"small\" disabled={busy} onClick={() => void copyLink()}>\n {'Copy pay link'}\n </Button>\n </BlockedControl>\n {unpaid ? (\n <BlockedControl blocked={!canSend} reason={SEND_REFUSAL}>\n <Button size=\"small\" disabled={busy} onClick={() => setConfirmingReplace(true)}>\n {'Replace link'}\n </Button>\n </BlockedControl>\n ) : null}\n </Stack>\n <Dialog open={confirmingReplace} onClose={() => setConfirmingReplace(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Replace the pay link?'}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {'The link you sent before will stop working: it will tell the customer the quote was updated, ' +\n 'and show nothing of it. Send them the new link.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirmingReplace(false)} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy} onClick={() => void replaceLink()}>\n {'Replace link'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nQuotePayLinkSection.displayName = 'QuotePayLinkSection'\n\nexport default QuotePayLinkSection\n"],"names":["CommerceModel","commerceSitePermissionRefusal","BlockedControl","useSnackbar","authorizedFetch","useUser","Button","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useCallback","useState","SEND_REFUSAL","QuotePayLinkSection","props","hostId","quoteId","quote","canSend","edited","data","user","enqueueSnackbar","url","setUrl","busy","setBusy","confirmingReplace","setConfirmingReplace","unpaid","isUnpaidQuote","ask","action","response","method","headers","body","JSON","stringify","payload","json","catch","ok","error","variant","allowDuplicate","String","copy","link","done","navigator","clipboard","writeText","persist","copyLink","replaceLink","spacing","color","label","value","size","fullWidth","slotProps","htmlInput","readOnly","inputLabel","shrink","onFocus","event","target","select","direction","sx","flexWrap","rowGap","blocked","reason","disabled","onClick","open","onClose","maxWidth","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/quote-pay-link.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { commerceSitePermissionRefusal } from '../../model/plugin-permissions'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\n\nconst SEND_REFUSAL = commerceSitePermissionRefusal('commerce.quotes.send')\n\nexport interface QuotePayLinkSectionProps {\n hostId: string\n quoteId: string\n quote: Pick<CommerceModel.HostOrder, 'status' | 'channel' | 'number' | 'quote'>\n /** Holds \"Send quotes and invoices to customers\" (`commerce.quotes.send`). */\n canSend: boolean\n /** The editor holds changes not saved yet, which the link does not show. */\n edited: boolean\n}\n\n/**\n * A quote's pay link (AGL-4094): the address the business sends the\n * customer, which opens the quote on the business's own site with a Pay\n * button. \"Copy pay link\" asks the server for it (it is signed there) and puts\n * it on the clipboard and on screen; \"Replace link\" makes a new one and the\n * one sent before stops opening the quote.\n *\n * The link shown is kept with the version it was made at, and drops as soon\n * as the quote's stored version (`quote.linkVersion`) moves past it: a\n * revision's first email and Replace link (here or anywhere else) both move\n * it, and the link from before then opens only \"This quote was updated\". A\n * link already on screen is fetched again at the new version; Copy pay link\n * never copies one older than the quote's.\n */\nexport function QuotePayLinkSection(props: QuotePayLinkSectionProps) {\n const { hostId, quoteId, quote, canSend, edited } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [signed, setSigned] = useState<{ url: string; version: number } | null>(null)\n const [busy, setBusy] = useState(false)\n const [confirmingReplace, setConfirmingReplace] = useState(false)\n const unpaid = CommerceModel.isUnpaidQuote(quote)\n const version = CommerceModel.quoteLinkVersion(quote.quote)\n // A link the server made at a version the stored quote has not caught up to yet is the newest one.\n const url = signed && signed.version >= version ? signed.url : ''\n\n const ask = useCallback(\n async (action: 'pay-link' | 'replace-link'): Promise<string | null> => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action, hostId, quoteId }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.url) {\n enqueueSnackbar(payload?.error ?? 'The pay link could not be made', { variant: 'error', allowDuplicate: true })\n return null\n }\n const made = Math.floor(Number(payload.linkVersion))\n setSigned({ url: String(payload.url), version: Number.isFinite(made) && made >= 1 ? made : version })\n return String(payload.url)\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, quoteId, version, enqueueSnackbar],\n )\n\n // The quote's link moved on (a revision was emailed, or the link replaced):\n // the one on screen opens nothing now, so fetch the current one in its place.\n const stale = signed !== null && signed.version < version\n useEffect(() => {\n if (stale) void ask('pay-link')\n // Once per version the quote moves to; `ask` changing does not ask again.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [stale, version])\n\n const copy = useCallback(\n async (link: string, done: string) => {\n try {\n await navigator.clipboard.writeText(link)\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch {\n // The link is on screen to copy by hand when the clipboard is refused.\n enqueueSnackbar('Copy the link below to send it', { variant: 'info', persist: false })\n }\n },\n [enqueueSnackbar],\n )\n\n const copyLink = useCallback(async () => {\n const link = url || (await ask('pay-link'))\n if (link) await copy(link, 'Pay link copied')\n }, [url, ask, copy])\n\n const replaceLink = useCallback(async () => {\n const link = await ask('replace-link')\n setConfirmingReplace(false)\n if (link) await copy(link, 'New pay link copied. The earlier link no longer opens the quote.')\n }, [ask, copy])\n\n return (\n <Stack spacing={1.5}>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Pay link'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {unpaid\n ? 'Send this link to the customer. It opens the quote on your site, where they can pay it.'\n : 'The link opens the paid quote and the customer’s order.'}\n </Typography>\n {edited && unpaid ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Save your changes first: the link opens the quote as it was last saved.'}\n </Typography>\n ) : null}\n </Stack>\n {url ? (\n <TextField\n label=\"Pay link\"\n value={url}\n size=\"small\"\n fullWidth\n slotProps={{ htmlInput: { readOnly: true }, inputLabel: { shrink: true } }}\n onFocus={(event) => event.target.select()}\n />\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n <BlockedControl blocked={!canSend} reason={SEND_REFUSAL}>\n <Button variant=\"outlined\" size=\"small\" disabled={busy} onClick={() => void copyLink()}>\n {'Copy pay link'}\n </Button>\n </BlockedControl>\n {unpaid ? (\n <BlockedControl blocked={!canSend} reason={SEND_REFUSAL}>\n <Button size=\"small\" disabled={busy} onClick={() => setConfirmingReplace(true)}>\n {'Replace link'}\n </Button>\n </BlockedControl>\n ) : null}\n </Stack>\n <Dialog open={confirmingReplace} onClose={() => setConfirmingReplace(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Replace the pay link?'}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {'The link you sent before will stop working: it will tell the customer the quote was updated, ' +\n 'and show nothing of it. Send them the new link.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirmingReplace(false)} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy} onClick={() => void replaceLink()}>\n {'Replace link'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nQuotePayLinkSection.displayName = 'QuotePayLinkSection'\n\nexport default QuotePayLinkSection\n"],"names":["CommerceModel","commerceSitePermissionRefusal","BlockedControl","useSnackbar","authorizedFetch","useUser","Button","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useState","SEND_REFUSAL","QuotePayLinkSection","props","hostId","quoteId","quote","canSend","edited","data","user","enqueueSnackbar","signed","setSigned","busy","setBusy","confirmingReplace","setConfirmingReplace","unpaid","isUnpaidQuote","version","quoteLinkVersion","url","ask","action","response","method","headers","body","JSON","stringify","payload","json","catch","ok","error","variant","allowDuplicate","made","Math","floor","Number","linkVersion","String","isFinite","stale","copy","link","done","navigator","clipboard","writeText","persist","copyLink","replaceLink","spacing","color","label","value","size","fullWidth","slotProps","htmlInput","readOnly","inputLabel","shrink","onFocus","event","target","select","direction","sx","flexWrap","rowGap","blocked","reason","disabled","onClick","open","onClose","maxWidth","displayName"],"mappings":"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"}
|
|
@@ -35,3 +35,14 @@ export declare namespace QuoteProofsSection {
|
|
|
35
35
|
var displayName: string;
|
|
36
36
|
}
|
|
37
37
|
export default QuoteProofsSection;
|
|
38
|
+
/**
|
|
39
|
+
* The files a quote's request came with (AGL-4098), as the customer's files:
|
|
40
|
+
* a picture for an image, a chip naming anything else, each downloaded
|
|
41
|
+
* through the quote's own route. They stay where the request keeps them, so a
|
|
42
|
+
* request deleted from the Inbox takes them with it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function QuoteRequestFilesSection(props: {
|
|
45
|
+
hostId: string;
|
|
46
|
+
quoteId: string;
|
|
47
|
+
quote: CommerceModel.HostOrder;
|
|
48
|
+
}): import("react").JSX.Element;
|
|
@@ -622,5 +622,73 @@ function dateTime(atMs) {
|
|
|
622
622
|
}
|
|
623
623
|
QuoteProofsSection.displayName = 'QuoteProofsSection';
|
|
624
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
|
+
}
|
|
625
693
|
|
|
626
694
|
//# sourceMappingURL=quote-proofs.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/quote-proofs.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { commerceSitePermissionRefusal } from '../../model/plugin-permissions'\nimport {\n checkVisitorUploadSelection,\n formatVisitorUploadBytes,\n visitorUploadIsInline,\n VISITOR_UPLOAD_ACCEPT,\n VISITOR_UPLOAD_MAX_BYTES,\n} from '@aglyn/aglyn/app-utils/visitor-upload'\nimport { sendVisitorUpload } from '@aglyn/aglyn/app-utils/visitor-upload-client'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Divider,\n Paper,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'\n\nconst REFUSAL = commerceSitePermissionRefusal('commerce.quotes')\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\n/** A file the console lists: a proof, or one the customer sent. Never its storage path. */\ninterface ListedFile {\n id: string\n name: string\n contentType: string\n size: number\n}\n\n/** An earlier revision as the route lists it. */\ntype RevisionView = Omit<CommerceModel.QuoteRevisionSnapshot, 'proofs' | 'responses'> & {\n proofs: ListedFile[]\n responses: Array<Omit<CommerceModel.QuoteCustomerResponse, 'files'> & { files?: ListedFile[] }>\n}\n\nexport interface QuoteProofsSectionProps {\n hostId: string\n quoteId: string\n quote: CommerceModel.HostOrder\n /** Holds \"Create and edit quotes\" (`commerce.quotes`). */\n canEdit: boolean\n /** A change here moved the quote's edit stamp; the editor saves against the new one. */\n onEdited: (updatedAtMs: number) => void\n}\n\nasync function postQuote(user: User, body: Record<string, unknown>): Promise<{ ok: boolean; payload: Record<string, any> }> {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n return { ok: response.ok, payload: await response.json().catch(() => ({})) }\n}\n\n/** One of the quote's files as a blob, fetched with the member's token; null when refused. */\nasync function fetchQuoteFile(\n user: User,\n body: { hostId: string; quoteId: string; fileId: string; revision?: number },\n): Promise<Blob | null> {\n try {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'file', ...body }),\n })\n return response.ok ? await response.blob() : null\n } catch {\n return null\n }\n}\n\nfunction dateTime(atMs: number | undefined): string {\n return atMs ? new Date(atMs).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) : ''\n}\n\n/** A picture of an image proof, loaded with the member's token. */\nfunction ProofThumbnail(props: { user: User; hostId: string; quoteId: string; file: ListedFile; revision?: number }) {\n const { user, hostId, quoteId, file, revision } = props\n const [src, setSrc] = useState<string | null>(null)\n useEffect(() => {\n let live = true\n let url = ''\n void fetchQuoteFile(user, { hostId, quoteId, fileId: file.id, ...(revision ? { revision } : {}) }).then((blob) => {\n if (!live || !blob) return\n url = URL.createObjectURL(blob)\n setSrc(url)\n })\n return () => {\n live = false\n if (url) URL.revokeObjectURL(url)\n }\n }, [user, hostId, quoteId, file.id, revision])\n return (\n <Box\n sx={(theme) => ({\n width: theme.spacing(16),\n height: theme.spacing(12),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n bgcolor: 'action.hover',\n borderRadius: 1,\n overflow: 'hidden',\n })}\n >\n {src ? (\n <Box component=\"img\" src={src} alt={file.name} sx={{ maxWidth: 1, maxHeight: 1, objectFit: 'contain' }} />\n ) : (\n <CircularProgress size={20} aria-label={`Loading ${file.name}`} />\n )}\n </Box>\n )\n}\n\n/** A file: a picture for an image, a chip naming it for anything else; both download it. */\nfunction QuoteFileTile(props: {\n user: User\n hostId: string\n quoteId: string\n file: ListedFile\n revision?: number\n onDownload: (file: ListedFile, revision?: number) => void\n onRemove?: () => void\n removeBlocked?: boolean\n}) {\n const { file, revision, onDownload, onRemove, removeBlocked } = props\n const image = visitorUploadIsInline(file.contentType)\n return (\n <Paper variant=\"outlined\" sx={{ p: 1 }}>\n <Stack spacing={1} sx={(theme) => ({ width: image ? theme.spacing(16) : 'auto', maxWidth: theme.spacing(32) })}>\n {image ? <ProofThumbnail {...props} /> : null}\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`${file.name} · ${formatVisitorUploadBytes(file.size)}`}\n onClick={() => onDownload(file, revision)}\n title={`Download ${file.name}`}\n />\n {onRemove ? (\n <BlockedControl blocked={Boolean(removeBlocked)} reason={REFUSAL}>\n <Button size=\"small\" color=\"inherit\" onClick={onRemove} aria-label={`Remove ${file.name}`}>\n {'Remove'}\n </Button>\n </BlockedControl>\n ) : null}\n </Stack>\n </Paper>\n )\n}\n\n/** What the customer said, with any files they sent. */\nfunction QuoteResponseNote(props: {\n response: Omit<CommerceModel.QuoteCustomerResponse, 'files'> & { files?: ListedFile[] }\n tile: (file: ListedFile) => ReactNode\n}) {\n const { response, tile } = props\n if (response.action === 'approve') {\n return (\n <Alert severity=\"success\">\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">{`Approved by ${response.name || 'the customer'} on ${dateTime(response.atMs)}.`}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[response.ip ? `From ${response.ip}` : '', response.userAgent ?? ''].filter(Boolean).join(' · ')}\n </Typography>\n </Stack>\n </Alert>\n )\n }\n return (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">{`Changes requested on ${dateTime(response.atMs)}:`}</Typography>\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-line' }}>\n {response.comment}\n </Typography>\n {response.files?.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {response.files.map((file) => tile(file))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n )\n}\n\n/**\n * A quote's proofs and revisions (AGL-4096), in its editor: the current\n * revision's proofs (a picture for an image, a chip for a PDF or a design\n * file), Add proof, what the customer last said with any files they sent,\n * \"Start revision N+1\", and every earlier revision as it was kept.\n */\nexport function QuoteProofsSection(props: QuoteProofsSectionProps) {\n const { hostId, quoteId, quote, canEdit, onEdited } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const section = quote.quote\n const revision = Math.max(1, Number(section?.revision ?? 1) || 1)\n const proofs = CommerceModel.currentQuoteProofs(section)\n const latest = CommerceModel.latestQuoteResponse(section)\n const unpaid = CommerceModel.isUnpaidQuote(quote)\n const addRefusal = CommerceModel.quoteProofChangeRefusal(quote, 'add')\n const removable = CommerceModel.quoteProofChangeRefusal(quote, 'remove') === null\n const [sending, setSending] = useState<{ done: number; total: number } | null>(null)\n const [busy, setBusy] = useState(false)\n const [confirmingRevision, setConfirmingRevision] = useState(false)\n const [history, setHistory] = useState<RevisionView[] | null>(null)\n const [historyError, setHistoryError] = useState('')\n const picker = useRef<HTMLInputElement>(null)\n\n const loadHistory = useCallback(async () => {\n setHistoryError('')\n const answer = await postQuote(user, { action: 'revisions', hostId, quoteId }).catch(() => null)\n if (!answer?.ok) {\n setHistoryError(String(answer?.payload?.error ?? 'Earlier revisions could not be loaded.'))\n return\n }\n setHistory(Array.isArray(answer.payload['revisions']) ? (answer.payload['revisions'] as RevisionView[]) : [])\n }, [user, hostId, quoteId])\n\n // Earlier revisions are read when there are some, and again after one is added.\n useEffect(() => {\n if (revision > 1) void loadHistory()\n }, [revision, loadHistory])\n\n const download = useCallback(\n async (file: ListedFile, fromRevision?: number) => {\n const blob = await fetchQuoteFile(user, { hostId, quoteId, fileId: file.id, ...(fromRevision ? { revision: fromRevision } : {}) })\n if (!blob) return void enqueueSnackbar('The file could not be downloaded', { variant: 'error', allowDuplicate: true })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = file.name\n link.click()\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n },\n [user, hostId, quoteId, enqueueSnackbar],\n )\n\n const addProofs = useCallback(\n async (files: File[]) => {\n const problem = checkVisitorUploadSelection(files, proofs.length, CommerceModel.QUOTE_MAX_PROOFS)\n if (problem) return void enqueueSnackbar(problem, { variant: 'error', allowDuplicate: true })\n setSending({ done: 0, total: files.length })\n try {\n for (const [index, file] of files.entries()) {\n let stamp = 0\n const sent = await sendVisitorUpload({\n file,\n requestTicket: async (request) => {\n const answer = await postQuote(user, { action: 'proof-ticket', hostId, quoteId, file: request })\n return answer.ok\n ? (answer.payload as { ticket: string; upload: { url: string; fields: Record<string, string> } })\n : { error: String(answer.payload['error'] ?? 'The proof could not be sent') }\n },\n finalize: async (ticket) => {\n const answer = await postQuote(user, { action: 'proof-finalize', hostId, quoteId, ticket })\n if (!answer.ok) return { error: String(answer.payload['error'] ?? 'The proof could not be kept') }\n stamp = Number(answer.payload['updatedAtMs']) || 0\n return { file: answer.payload['proof'] }\n },\n })\n if ('error' in sent) {\n enqueueSnackbar(sent.error, { variant: 'error', allowDuplicate: true })\n break\n }\n if (stamp) onEdited(stamp)\n setSending({ done: index + 1, total: files.length })\n }\n } finally {\n setSending(null)\n }\n },\n [user, hostId, quoteId, proofs.length, onEdited, enqueueSnackbar],\n )\n\n const removeProof = useCallback(\n async (proofId: string) => {\n setBusy(true)\n try {\n const answer = await postQuote(user, { action: 'proof-remove', hostId, quoteId, proofId })\n if (!answer.ok) {\n return void enqueueSnackbar(String(answer.payload['error'] ?? 'The proof could not be removed'), {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n onEdited(Number(answer.payload['updatedAtMs']) || 0)\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, quoteId, onEdited, enqueueSnackbar],\n )\n\n const startRevision = useCallback(async () => {\n setBusy(true)\n try {\n const answer = await postQuote(user, { action: 'start-revision', hostId, quoteId })\n if (!answer.ok) {\n return void enqueueSnackbar(String(answer.payload['error'] ?? 'The revision could not be started'), {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n onEdited(Number(answer.payload['updatedAtMs']) || 0)\n enqueueSnackbar(`Revision ${Number(answer.payload['revision']) || revision + 1} started`, { variant: 'success', persist: false })\n setConfirmingRevision(false)\n } finally {\n setBusy(false)\n }\n }, [user, hostId, quoteId, revision, onEdited, enqueueSnackbar])\n\n const tile = (file: ListedFile, fromRevision?: number, onRemove?: () => void) => (\n <QuoteFileTile\n key={`${fromRevision ?? 'current'}-${file.id}`}\n user={user}\n hostId={hostId}\n quoteId={quoteId}\n file={file}\n revision={fromRevision}\n onDownload={(picked, at) => void download(picked, at)}\n onRemove={onRemove}\n removeBlocked={!canEdit}\n />\n )\n\n const earlier = (history ?? []).filter((entry) => Number(entry.revision) < revision).reverse()\n\n return (\n <Stack spacing={1.5}>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{`Proofs · Revision ${revision}`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {section?.requireProofApproval\n ? 'The customer sees these on the quote page and approves them before they can pay.'\n : 'The customer sees these on the quote page, and can approve them or ask for changes. Paying doesn’t wait on it.'}\n </Typography>\n </Stack>\n\n {latest ? <QuoteResponseNote response={latest} tile={(file) => tile(file)} /> : null}\n\n {proofs.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {proofs.map((proof) =>\n tile(proof, undefined, removable ? () => void removeProof(proof.id) : undefined),\n )}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No proofs on this revision yet. Add an image, a PDF or a design file for the customer to approve.'}\n </Typography>\n )}\n\n {unpaid ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap', alignItems: 'center' }}>\n <input\n ref={picker}\n type=\"file\"\n multiple\n hidden\n accept={VISITOR_UPLOAD_ACCEPT}\n data-testid=\"quote-proof-picker\"\n onChange={(event) => {\n const files = Array.from(event.target.files ?? [])\n event.target.value = ''\n if (files.length) void addProofs(files)\n }}\n />\n <BlockedControl\n blocked={!canEdit || Boolean(addRefusal)}\n reason={!canEdit ? REFUSAL : addRefusal ? CommerceModel.quoteProofRefusalMessage(addRefusal) : ''}\n >\n <Button variant=\"outlined\" size=\"small\" disabled={busy || Boolean(sending)} onClick={() => picker.current?.click()}>\n {sending ? `Sending ${sending.done + 1} of ${sending.total}…` : 'Add proof'}\n </Button>\n </BlockedControl>\n <BlockedControl blocked={!canEdit} reason={REFUSAL}>\n <Button size=\"small\" disabled={busy || Boolean(sending)} onClick={() => setConfirmingRevision(true)}>\n {`Start revision ${revision + 1}`}\n </Button>\n </BlockedControl>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${CommerceModel.QUOTE_MAX_PROOFS} files, ${formatVisitorUploadBytes(VISITOR_UPLOAD_MAX_BYTES)} each.`}\n </Typography>\n </Stack>\n ) : null}\n\n {revision > 1 ? (\n <Stack spacing={1.5}>\n <Divider />\n <Typography variant=\"subtitle2\">{'Earlier revisions'}</Typography>\n {historyError ? <Alert severity=\"error\">{historyError}</Alert> : null}\n {history === null && !historyError ? <CircularProgress size={20} aria-label=\"Loading earlier revisions\" /> : null}\n {earlier.map((entry) => (\n <Paper key={entry.revision} variant=\"outlined\" sx={{ p: 2 }}>\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'fontWeightMedium' }}>{`Revision ${entry.revision}`}</Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={CommerceModel.QUOTE_STAGE_LABELS[entry.stage] ?? entry.stage}\n color={CommerceModel.QUOTE_STATUS_COLOR[entry.stage] ?? 'default'}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Replaced ${dateTime(entry.closedAtMs)}`}\n </Typography>\n </Stack>\n {entry.proofs.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {entry.proofs.map((proof) => tile(proof, entry.revision))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No proofs.'}\n </Typography>\n )}\n {entry.responses.map((response, index) => (\n <QuoteResponseNote key={index} response={response} tile={(file) => tile(file, entry.revision)} />\n ))}\n </Stack>\n </Paper>\n ))}\n </Stack>\n ) : null}\n\n <Dialog open={confirmingRevision} onClose={() => setConfirmingRevision(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{`Start revision ${revision + 1}?`}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {`Revision ${revision} is kept as it is now, with its proofs and the customer’s answers. ` +\n `Revision ${revision + 1} starts with no proofs, ready for new ones and any changes to the quote.`}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirmingRevision(false)} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy} onClick={() => void startRevision()}>\n {`Start revision ${revision + 1}`}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nQuoteProofsSection.displayName = 'QuoteProofsSection'\n\nexport default QuoteProofsSection\n"],"names":["CommerceModel","commerceSitePermissionRefusal","checkVisitorUploadSelection","formatVisitorUploadBytes","visitorUploadIsInline","VISITOR_UPLOAD_ACCEPT","VISITOR_UPLOAD_MAX_BYTES","sendVisitorUpload","BlockedControl","useSnackbar","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Divider","Paper","Stack","Typography","useCallback","useEffect","useRef","useState","REFUSAL","postQuote","user","body","response","method","headers","JSON","stringify","ok","payload","json","catch","fetchQuoteFile","action","blob","dateTime","atMs","Date","toLocaleString","undefined","dateStyle","timeStyle","ProofThumbnail","props","hostId","quoteId","file","revision","src","setSrc","live","url","fileId","id","then","URL","createObjectURL","revokeObjectURL","sx","theme","width","spacing","height","display","alignItems","justifyContent","bgcolor","borderRadius","overflow","component","alt","name","maxWidth","maxHeight","objectFit","size","aria-label","QuoteFileTile","onDownload","onRemove","removeBlocked","image","contentType","variant","p","label","onClick","title","blocked","Boolean","reason","color","QuoteResponseNote","tile","severity","ip","userAgent","filter","join","whiteSpace","comment","files","length","direction","useFlexGap","flexWrap","map","QuoteProofsSection","quote","canEdit","onEdited","data","enqueueSnackbar","section","Math","max","Number","proofs","currentQuoteProofs","latest","latestQuoteResponse","unpaid","isUnpaidQuote","addRefusal","quoteProofChangeRefusal","removable","sending","setSending","busy","setBusy","confirmingRevision","setConfirmingRevision","history","setHistory","historyError","setHistoryError","picker","loadHistory","answer","String","error","Array","isArray","download","fromRevision","allowDuplicate","link","document","createElement","href","click","setTimeout","addProofs","problem","QUOTE_MAX_PROOFS","done","total","index","entries","stamp","sent","requestTicket","request","finalize","ticket","removeProof","proofId","startRevision","persist","picked","at","earlier","entry","reverse","requireProofApproval","proof","input","ref","type","multiple","hidden","accept","data-testid","onChange","event","from","target","value","quoteProofRefusalMessage","disabled","current","rowGap","fontWeight","QUOTE_STAGE_LABELS","stage","QUOTE_STATUS_COLOR","closedAtMs","responses","open","onClose","fullWidth","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/quote-proofs.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport { commerceSitePermissionRefusal } from '../../model/plugin-permissions'\nimport {\n checkVisitorUploadSelection,\n formatVisitorUploadBytes,\n visitorUploadIsInline,\n VISITOR_UPLOAD_ACCEPT,\n VISITOR_UPLOAD_MAX_BYTES,\n} from '@aglyn/aglyn/app-utils/visitor-upload'\nimport { sendVisitorUpload } from '@aglyn/aglyn/app-utils/visitor-upload-client'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Divider,\n Paper,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'\n\nconst REFUSAL = commerceSitePermissionRefusal('commerce.quotes')\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\n/** A file the console lists: a proof, or one the customer sent. Never its storage path. */\ninterface ListedFile {\n id: string\n name: string\n contentType: string\n size: number\n}\n\n/** An earlier revision as the route lists it. */\ntype RevisionView = Omit<CommerceModel.QuoteRevisionSnapshot, 'proofs' | 'responses'> & {\n proofs: ListedFile[]\n responses: Array<Omit<CommerceModel.QuoteCustomerResponse, 'files'> & { files?: ListedFile[] }>\n}\n\nexport interface QuoteProofsSectionProps {\n hostId: string\n quoteId: string\n quote: CommerceModel.HostOrder\n /** Holds \"Create and edit quotes\" (`commerce.quotes`). */\n canEdit: boolean\n /** A change here moved the quote's edit stamp; the editor saves against the new one. */\n onEdited: (updatedAtMs: number) => void\n}\n\nasync function postQuote(user: User, body: Record<string, unknown>): Promise<{ ok: boolean; payload: Record<string, any> }> {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n return { ok: response.ok, payload: await response.json().catch(() => ({})) }\n}\n\n/** One of the quote's files as a blob, fetched with the member's token; null when refused. */\nasync function fetchQuoteFile(\n user: User,\n body: { hostId: string; quoteId: string; fileId: string; revision?: number },\n): Promise<Blob | null> {\n try {\n const response = await authorizedFetch(user, '/api/commerce/quote', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'file', ...body }),\n })\n return response.ok ? await response.blob() : null\n } catch {\n return null\n }\n}\n\nfunction dateTime(atMs: number | undefined): string {\n return atMs ? new Date(atMs).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) : ''\n}\n\n/** A picture of an image proof, loaded with the member's token. */\nfunction ProofThumbnail(props: { user: User; hostId: string; quoteId: string; file: ListedFile; revision?: number }) {\n const { user, hostId, quoteId, file, revision } = props\n const [src, setSrc] = useState<string | null>(null)\n useEffect(() => {\n let live = true\n let url = ''\n void fetchQuoteFile(user, { hostId, quoteId, fileId: file.id, ...(revision ? { revision } : {}) }).then((blob) => {\n if (!live || !blob) return\n url = URL.createObjectURL(blob)\n setSrc(url)\n })\n return () => {\n live = false\n if (url) URL.revokeObjectURL(url)\n }\n }, [user, hostId, quoteId, file.id, revision])\n return (\n <Box\n sx={(theme) => ({\n width: theme.spacing(16),\n height: theme.spacing(12),\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n bgcolor: 'action.hover',\n borderRadius: 1,\n overflow: 'hidden',\n })}\n >\n {src ? (\n <Box component=\"img\" src={src} alt={file.name} sx={{ maxWidth: 1, maxHeight: 1, objectFit: 'contain' }} />\n ) : (\n <CircularProgress size={20} aria-label={`Loading ${file.name}`} />\n )}\n </Box>\n )\n}\n\n/** A file: a picture for an image, a chip naming it for anything else; both download it. */\nfunction QuoteFileTile(props: {\n user: User\n hostId: string\n quoteId: string\n file: ListedFile\n revision?: number\n onDownload: (file: ListedFile, revision?: number) => void\n onRemove?: () => void\n removeBlocked?: boolean\n}) {\n const { file, revision, onDownload, onRemove, removeBlocked } = props\n const image = visitorUploadIsInline(file.contentType)\n return (\n <Paper variant=\"outlined\" sx={{ p: 1 }}>\n <Stack spacing={1} sx={(theme) => ({ width: image ? theme.spacing(16) : 'auto', maxWidth: theme.spacing(32) })}>\n {image ? <ProofThumbnail {...props} /> : null}\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`${file.name} · ${formatVisitorUploadBytes(file.size)}`}\n onClick={() => onDownload(file, revision)}\n title={`Download ${file.name}`}\n />\n {onRemove ? (\n <BlockedControl blocked={Boolean(removeBlocked)} reason={REFUSAL}>\n <Button size=\"small\" color=\"inherit\" onClick={onRemove} aria-label={`Remove ${file.name}`}>\n {'Remove'}\n </Button>\n </BlockedControl>\n ) : null}\n </Stack>\n </Paper>\n )\n}\n\n/** What the customer said, with any files they sent. */\nfunction QuoteResponseNote(props: {\n response: Omit<CommerceModel.QuoteCustomerResponse, 'files'> & { files?: ListedFile[] }\n tile: (file: ListedFile) => ReactNode\n}) {\n const { response, tile } = props\n if (response.action === 'approve') {\n return (\n <Alert severity=\"success\">\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">{`Approved by ${response.name || 'the customer'} on ${dateTime(response.atMs)}.`}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[response.ip ? `From ${response.ip}` : '', response.userAgent ?? ''].filter(Boolean).join(' · ')}\n </Typography>\n </Stack>\n </Alert>\n )\n }\n return (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">{`Changes requested on ${dateTime(response.atMs)}:`}</Typography>\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-line' }}>\n {response.comment}\n </Typography>\n {response.files?.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {response.files.map((file) => tile(file))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n )\n}\n\n/**\n * A quote's proofs and revisions (AGL-4096), in its editor: the current\n * revision's proofs (a picture for an image, a chip for a PDF or a design\n * file), Add proof, what the customer last said with any files they sent,\n * \"Start revision N+1\", and every earlier revision as it was kept.\n */\nexport function QuoteProofsSection(props: QuoteProofsSectionProps) {\n const { hostId, quoteId, quote, canEdit, onEdited } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const section = quote.quote\n const revision = Math.max(1, Number(section?.revision ?? 1) || 1)\n const proofs = CommerceModel.currentQuoteProofs(section)\n const latest = CommerceModel.latestQuoteResponse(section)\n const unpaid = CommerceModel.isUnpaidQuote(quote)\n const addRefusal = CommerceModel.quoteProofChangeRefusal(quote, 'add')\n const removable = CommerceModel.quoteProofChangeRefusal(quote, 'remove') === null\n const [sending, setSending] = useState<{ done: number; total: number } | null>(null)\n const [busy, setBusy] = useState(false)\n const [confirmingRevision, setConfirmingRevision] = useState(false)\n const [history, setHistory] = useState<RevisionView[] | null>(null)\n const [historyError, setHistoryError] = useState('')\n const picker = useRef<HTMLInputElement>(null)\n\n const loadHistory = useCallback(async () => {\n setHistoryError('')\n const answer = await postQuote(user, { action: 'revisions', hostId, quoteId }).catch(() => null)\n if (!answer?.ok) {\n setHistoryError(String(answer?.payload?.error ?? 'Earlier revisions could not be loaded.'))\n return\n }\n setHistory(Array.isArray(answer.payload['revisions']) ? (answer.payload['revisions'] as RevisionView[]) : [])\n }, [user, hostId, quoteId])\n\n // Earlier revisions are read when there are some, and again after one is added.\n useEffect(() => {\n if (revision > 1) void loadHistory()\n }, [revision, loadHistory])\n\n const download = useCallback(\n async (file: ListedFile, fromRevision?: number) => {\n const blob = await fetchQuoteFile(user, { hostId, quoteId, fileId: file.id, ...(fromRevision ? { revision: fromRevision } : {}) })\n if (!blob) return void enqueueSnackbar('The file could not be downloaded', { variant: 'error', allowDuplicate: true })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = file.name\n link.click()\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n },\n [user, hostId, quoteId, enqueueSnackbar],\n )\n\n const addProofs = useCallback(\n async (files: File[]) => {\n const problem = checkVisitorUploadSelection(files, proofs.length, CommerceModel.QUOTE_MAX_PROOFS)\n if (problem) return void enqueueSnackbar(problem, { variant: 'error', allowDuplicate: true })\n setSending({ done: 0, total: files.length })\n try {\n for (const [index, file] of files.entries()) {\n let stamp = 0\n const sent = await sendVisitorUpload({\n file,\n requestTicket: async (request) => {\n const answer = await postQuote(user, { action: 'proof-ticket', hostId, quoteId, file: request })\n return answer.ok\n ? (answer.payload as { ticket: string; upload: { url: string; fields: Record<string, string> } })\n : { error: String(answer.payload['error'] ?? 'The proof could not be sent') }\n },\n finalize: async (ticket) => {\n const answer = await postQuote(user, { action: 'proof-finalize', hostId, quoteId, ticket })\n if (!answer.ok) return { error: String(answer.payload['error'] ?? 'The proof could not be kept') }\n stamp = Number(answer.payload['updatedAtMs']) || 0\n return { file: answer.payload['proof'] }\n },\n })\n if ('error' in sent) {\n enqueueSnackbar(sent.error, { variant: 'error', allowDuplicate: true })\n break\n }\n if (stamp) onEdited(stamp)\n setSending({ done: index + 1, total: files.length })\n }\n } finally {\n setSending(null)\n }\n },\n [user, hostId, quoteId, proofs.length, onEdited, enqueueSnackbar],\n )\n\n const removeProof = useCallback(\n async (proofId: string) => {\n setBusy(true)\n try {\n const answer = await postQuote(user, { action: 'proof-remove', hostId, quoteId, proofId })\n if (!answer.ok) {\n return void enqueueSnackbar(String(answer.payload['error'] ?? 'The proof could not be removed'), {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n onEdited(Number(answer.payload['updatedAtMs']) || 0)\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, quoteId, onEdited, enqueueSnackbar],\n )\n\n const startRevision = useCallback(async () => {\n setBusy(true)\n try {\n const answer = await postQuote(user, { action: 'start-revision', hostId, quoteId })\n if (!answer.ok) {\n return void enqueueSnackbar(String(answer.payload['error'] ?? 'The revision could not be started'), {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n onEdited(Number(answer.payload['updatedAtMs']) || 0)\n enqueueSnackbar(`Revision ${Number(answer.payload['revision']) || revision + 1} started`, { variant: 'success', persist: false })\n setConfirmingRevision(false)\n } finally {\n setBusy(false)\n }\n }, [user, hostId, quoteId, revision, onEdited, enqueueSnackbar])\n\n const tile = (file: ListedFile, fromRevision?: number, onRemove?: () => void) => (\n <QuoteFileTile\n key={`${fromRevision ?? 'current'}-${file.id}`}\n user={user}\n hostId={hostId}\n quoteId={quoteId}\n file={file}\n revision={fromRevision}\n onDownload={(picked, at) => void download(picked, at)}\n onRemove={onRemove}\n removeBlocked={!canEdit}\n />\n )\n\n const earlier = (history ?? []).filter((entry) => Number(entry.revision) < revision).reverse()\n\n return (\n <Stack spacing={1.5}>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{`Proofs · Revision ${revision}`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {section?.requireProofApproval\n ? 'The customer sees these on the quote page and approves them before they can pay.'\n : 'The customer sees these on the quote page, and can approve them or ask for changes. Paying doesn’t wait on it.'}\n </Typography>\n </Stack>\n\n {latest ? <QuoteResponseNote response={latest} tile={(file) => tile(file)} /> : null}\n\n {proofs.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {proofs.map((proof) =>\n tile(proof, undefined, removable ? () => void removeProof(proof.id) : undefined),\n )}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No proofs on this revision yet. Add an image, a PDF or a design file for the customer to approve.'}\n </Typography>\n )}\n\n {unpaid ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap', alignItems: 'center' }}>\n <input\n ref={picker}\n type=\"file\"\n multiple\n hidden\n accept={VISITOR_UPLOAD_ACCEPT}\n data-testid=\"quote-proof-picker\"\n onChange={(event) => {\n const files = Array.from(event.target.files ?? [])\n event.target.value = ''\n if (files.length) void addProofs(files)\n }}\n />\n <BlockedControl\n blocked={!canEdit || Boolean(addRefusal)}\n reason={!canEdit ? REFUSAL : addRefusal ? CommerceModel.quoteProofRefusalMessage(addRefusal) : ''}\n >\n <Button variant=\"outlined\" size=\"small\" disabled={busy || Boolean(sending)} onClick={() => picker.current?.click()}>\n {sending ? `Sending ${sending.done + 1} of ${sending.total}…` : 'Add proof'}\n </Button>\n </BlockedControl>\n <BlockedControl blocked={!canEdit} reason={REFUSAL}>\n <Button size=\"small\" disabled={busy || Boolean(sending)} onClick={() => setConfirmingRevision(true)}>\n {`Start revision ${revision + 1}`}\n </Button>\n </BlockedControl>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${CommerceModel.QUOTE_MAX_PROOFS} files, ${formatVisitorUploadBytes(VISITOR_UPLOAD_MAX_BYTES)} each.`}\n </Typography>\n </Stack>\n ) : null}\n\n {revision > 1 ? (\n <Stack spacing={1.5}>\n <Divider />\n <Typography variant=\"subtitle2\">{'Earlier revisions'}</Typography>\n {historyError ? <Alert severity=\"error\">{historyError}</Alert> : null}\n {history === null && !historyError ? <CircularProgress size={20} aria-label=\"Loading earlier revisions\" /> : null}\n {earlier.map((entry) => (\n <Paper key={entry.revision} variant=\"outlined\" sx={{ p: 2 }}>\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'fontWeightMedium' }}>{`Revision ${entry.revision}`}</Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={CommerceModel.QUOTE_STAGE_LABELS[entry.stage] ?? entry.stage}\n color={CommerceModel.QUOTE_STATUS_COLOR[entry.stage] ?? 'default'}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Replaced ${dateTime(entry.closedAtMs)}`}\n </Typography>\n </Stack>\n {entry.proofs.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {entry.proofs.map((proof) => tile(proof, entry.revision))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No proofs.'}\n </Typography>\n )}\n {entry.responses.map((response, index) => (\n <QuoteResponseNote key={index} response={response} tile={(file) => tile(file, entry.revision)} />\n ))}\n </Stack>\n </Paper>\n ))}\n </Stack>\n ) : null}\n\n <Dialog open={confirmingRevision} onClose={() => setConfirmingRevision(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{`Start revision ${revision + 1}?`}</DialogTitle>\n <DialogContent>\n <DialogContentText>\n {`Revision ${revision} is kept as it is now, with its proofs and the customer’s answers. ` +\n `Revision ${revision + 1} starts with no proofs, ready for new ones and any changes to the quote.`}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirmingRevision(false)} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" disabled={busy} onClick={() => void startRevision()}>\n {`Start revision ${revision + 1}`}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nQuoteProofsSection.displayName = 'QuoteProofsSection'\n\nexport default QuoteProofsSection\n\n/**\n * The files a quote's request came with (AGL-4098), as the customer's files:\n * a picture for an image, a chip naming anything else, each downloaded\n * through the quote's own route. They stay where the request keeps them, so a\n * request deleted from the Inbox takes them with it.\n */\nexport function QuoteRequestFilesSection(props: { hostId: string; quoteId: string; quote: CommerceModel.HostOrder }) {\n const { hostId, quoteId, quote } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const request = quote.quote?.request\n const files = CommerceModel.quoteRequestFileViews(quote.quote?.requestFiles)\n const download = useCallback(\n async (file: ListedFile) => {\n const blob = await fetchQuoteFile(user, { hostId, quoteId, fileId: file.id })\n if (!blob) {\n return void enqueueSnackbar('The file could not be downloaded. The request may have been deleted from the Inbox.', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = file.name\n link.click()\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n },\n [user, hostId, quoteId, enqueueSnackbar],\n )\n if (!request || !user) return null\n const when = request.receivedAtMs ? ` on ${dateTime(request.receivedAtMs)}` : ''\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'From the request'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Sent through “${request.formName}”${when}.` + (files.length ? ' The files they sent:' : ' They sent no files.')}\n </Typography>\n {files.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {files.map((file) => (\n <QuoteFileTile\n key={file.id}\n user={user}\n hostId={hostId}\n quoteId={quoteId}\n file={file}\n onDownload={(picked) => void download(picked)}\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n )\n}\n"],"names":["CommerceModel","commerceSitePermissionRefusal","checkVisitorUploadSelection","formatVisitorUploadBytes","visitorUploadIsInline","VISITOR_UPLOAD_ACCEPT","VISITOR_UPLOAD_MAX_BYTES","sendVisitorUpload","BlockedControl","useSnackbar","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Divider","Paper","Stack","Typography","useCallback","useEffect","useRef","useState","REFUSAL","postQuote","user","body","response","method","headers","JSON","stringify","ok","payload","json","catch","fetchQuoteFile","action","blob","dateTime","atMs","Date","toLocaleString","undefined","dateStyle","timeStyle","ProofThumbnail","props","hostId","quoteId","file","revision","src","setSrc","live","url","fileId","id","then","URL","createObjectURL","revokeObjectURL","sx","theme","width","spacing","height","display","alignItems","justifyContent","bgcolor","borderRadius","overflow","component","alt","name","maxWidth","maxHeight","objectFit","size","aria-label","QuoteFileTile","onDownload","onRemove","removeBlocked","image","contentType","variant","p","label","onClick","title","blocked","Boolean","reason","color","QuoteResponseNote","tile","severity","ip","userAgent","filter","join","whiteSpace","comment","files","length","direction","useFlexGap","flexWrap","map","QuoteProofsSection","quote","canEdit","onEdited","data","enqueueSnackbar","section","Math","max","Number","proofs","currentQuoteProofs","latest","latestQuoteResponse","unpaid","isUnpaidQuote","addRefusal","quoteProofChangeRefusal","removable","sending","setSending","busy","setBusy","confirmingRevision","setConfirmingRevision","history","setHistory","historyError","setHistoryError","picker","loadHistory","answer","String","error","Array","isArray","download","fromRevision","allowDuplicate","link","document","createElement","href","click","setTimeout","addProofs","problem","QUOTE_MAX_PROOFS","done","total","index","entries","stamp","sent","requestTicket","request","finalize","ticket","removeProof","proofId","startRevision","persist","picked","at","earlier","entry","reverse","requireProofApproval","proof","input","ref","type","multiple","hidden","accept","data-testid","onChange","event","from","target","value","quoteProofRefusalMessage","disabled","current","rowGap","fontWeight","QUOTE_STAGE_LABELS","stage","QUOTE_STATUS_COLOR","closedAtMs","responses","open","onClose","fullWidth","displayName","QuoteRequestFilesSection","quoteRequestFileViews","requestFiles","when","receivedAtMs","formName"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 * as CommerceModel from '../../model';
|
|
18
|
+
export interface QuoteSendSectionProps {
|
|
19
|
+
hostId: string;
|
|
20
|
+
quoteId: string;
|
|
21
|
+
quote: CommerceModel.HostOrder;
|
|
22
|
+
/** Holds "Send quotes and invoices to customers" (`commerce.quotes.send`). */
|
|
23
|
+
canSend: boolean;
|
|
24
|
+
/** The editor holds changes not saved yet, which the email would not show. */
|
|
25
|
+
edited: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Emailing the quote to the customer (AGL-4097): when it was last sent and to
|
|
29
|
+
* whom, and Send quote, Send again or Send revision N, which opens a short
|
|
30
|
+
* form (To, Subject, Message). The server writes the email in the site's
|
|
31
|
+
* look with the proof, the PDF and the pay button, in the customer's
|
|
32
|
+
* conversation with the business.
|
|
33
|
+
*
|
|
34
|
+
* Send invoice (AGL-4099) sits beside it: for an unpaid quote, an invoice for
|
|
35
|
+
* a customer who needs one to pay (with the same pay link); for a paid one,
|
|
36
|
+
* the invoice as their record. Both reply into the quote's conversation.
|
|
37
|
+
*/
|
|
38
|
+
export declare function QuoteSendSection(props: QuoteSendSectionProps): import("react").JSX.Element;
|
|
39
|
+
export declare namespace QuoteSendSection {
|
|
40
|
+
var displayName: string;
|
|
41
|
+
}
|
|
42
|
+
export default QuoteSendSection;
|