@metron-labs/quote-react 0.1.34 → 0.1.35
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/dist/app/QuickQuote.js +50 -6
- package/dist/embed/quote.js +12 -12
- package/package.json +1 -1
package/dist/app/QuickQuote.js
CHANGED
|
@@ -11,6 +11,7 @@ import BasketStage from "./stages/BasketStage";
|
|
|
11
11
|
import { CartProvider, useCart } from "../lib/cart/CartContext";
|
|
12
12
|
import { clientHeaders, readJson } from "../lib/api/boqClient";
|
|
13
13
|
import { getPricingBreakdown } from "../util/pricing";
|
|
14
|
+
const SAVED_QUOTE_KEY = "metron-quick-quote";
|
|
14
15
|
// Its own provider, separate from the estimating tools' - a quote in progress and a set
|
|
15
16
|
// of calculator results are different baskets.
|
|
16
17
|
export default function QuickQuote(props) {
|
|
@@ -24,7 +25,7 @@ function QuickQuoteInner({ fullPage = false, hideHeader = false, internal = fals
|
|
|
24
25
|
const [processingStep, setProcessingStep] = useState(0);
|
|
25
26
|
const [scheduleResult, setScheduleResult] = useState(null);
|
|
26
27
|
const [scheduleReviewGateComplete, setScheduleReviewGateComplete] = useState(false);
|
|
27
|
-
const { cart, addToCart, removeFromCart, updateCartQty, updateCartSellUnits, updateCartConversions, clearCart } = useCart();
|
|
28
|
+
const { cart, addToCart, addManyToCart, removeFromCart, updateCartQty, updateCartSellUnits, updateCartConversions, clearCart } = useCart();
|
|
28
29
|
const [cartSeeded, setCartSeeded] = useState(false);
|
|
29
30
|
const resolvedConversionInputs = useRef(new Map());
|
|
30
31
|
const rootRef = useRef(null);
|
|
@@ -82,13 +83,49 @@ function QuickQuoteInner({ fullPage = false, hideHeader = false, internal = fals
|
|
|
82
83
|
})
|
|
83
84
|
.catch((error) => console.error("Catalogue conversion resolution failed", error));
|
|
84
85
|
}, [cart, updateCartConversions]);
|
|
86
|
+
// The quote survives the widget restarting - the host re-rendering the page under us
|
|
87
|
+
// (Turbo restoring its own entry), or a refresh. It lives in this tab's sessionStorage and
|
|
88
|
+
// the URL hash says which step to show. Restored after mount, not in the initial state, so
|
|
89
|
+
// a server-rendered host doesn't hit a hydration mismatch.
|
|
90
|
+
const [restored, setRestored] = useState(false);
|
|
85
91
|
useEffect(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
const hash = window.location.hash;
|
|
93
|
+
let saved = null;
|
|
94
|
+
try {
|
|
95
|
+
saved = JSON.parse(window.sessionStorage.getItem(SAVED_QUOTE_KEY) ?? "null");
|
|
96
|
+
}
|
|
97
|
+
catch { }
|
|
98
|
+
if (saved?.scheduleResult) {
|
|
99
|
+
setEntryMode("schedule");
|
|
100
|
+
setScheduleResult(saved.scheduleResult);
|
|
101
|
+
setScheduleReviewGateComplete(saved.reviewGateComplete);
|
|
102
|
+
setCartSeeded(saved.cartSeeded);
|
|
103
|
+
addManyToCart(Object.values(saved.cart));
|
|
104
|
+
if (hash === "#quote-request")
|
|
105
|
+
setStage("basket");
|
|
106
|
+
else if (hash === "#matched-products")
|
|
107
|
+
setStage("results");
|
|
108
|
+
}
|
|
109
|
+
else if (hash === "#matched-products" || hash === "#quote-request") {
|
|
110
|
+
const url = new URL(window.location.href);
|
|
111
|
+
url.hash = "";
|
|
112
|
+
window.history.replaceState(window.history.state, "", url);
|
|
113
|
+
}
|
|
114
|
+
setRestored(true);
|
|
91
115
|
}, []);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!restored)
|
|
118
|
+
return;
|
|
119
|
+
// ponytail: rewrites the whole quote on every basket edit; fine at schedule sizes,
|
|
120
|
+
// debounce it if large schedules make typing in quantities lag.
|
|
121
|
+
try {
|
|
122
|
+
if (!scheduleResult)
|
|
123
|
+
window.sessionStorage.removeItem(SAVED_QUOTE_KEY);
|
|
124
|
+
else
|
|
125
|
+
window.sessionStorage.setItem(SAVED_QUOTE_KEY, JSON.stringify({ scheduleResult, reviewGateComplete: scheduleReviewGateComplete, cartSeeded, cart }));
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
}, [restored, scheduleResult, scheduleReviewGateComplete, cartSeeded, cart]);
|
|
92
129
|
const writeQuoteHistory = useCallback((next) => {
|
|
93
130
|
if (typeof window === "undefined")
|
|
94
131
|
return;
|
|
@@ -102,8 +139,10 @@ function QuickQuoteInner({ fullPage = false, hideHeader = false, internal = fals
|
|
|
102
139
|
const stale = next === "results" && (window.location.hash === "#matched-products" || window.location.hash === "#quote-request");
|
|
103
140
|
window.history[stale ? "replaceState" : "pushState"]({ boqQuoteStage: next }, "", url);
|
|
104
141
|
}, []);
|
|
142
|
+
const leavingBasket = useRef(false);
|
|
105
143
|
useEffect(() => {
|
|
106
144
|
const onPopState = (event) => {
|
|
145
|
+
leavingBasket.current = false;
|
|
107
146
|
const hash = window.location.hash;
|
|
108
147
|
if (hash === "#quote-request")
|
|
109
148
|
setStage("basket");
|
|
@@ -134,7 +173,12 @@ function QuickQuoteInner({ fullPage = false, hideHeader = false, internal = fals
|
|
|
134
173
|
const backToResults = useCallback(() => {
|
|
135
174
|
// Step back over the basket entry goToBasket pushed (popstate picks the screen), rather
|
|
136
175
|
// than rewriting it: each rewrite left an extra results entry the browser back had to cross.
|
|
176
|
+
// history.back() is async: the basket stays up until popstate, so a double click would
|
|
177
|
+
// otherwise step back twice - past results to the host's entry, which re-renders the page.
|
|
137
178
|
if (window.location.hash === "#quote-request") {
|
|
179
|
+
if (leavingBasket.current)
|
|
180
|
+
return;
|
|
181
|
+
leavingBasket.current = true;
|
|
138
182
|
window.history.back();
|
|
139
183
|
return;
|
|
140
184
|
}
|