/* =========================================================== Quote Builder (flagship) + generated quote view =========================================================== */ function QuoteLine({ line }) { const quote = useQuote(); const specs = Object.entries(line.attributes || {}).filter(([k, v]) => v && v !== "—").slice(0, 3); const inter = (line.part_numbers || []).filter(p => p.kind !== "OEM").map(p => p.number).slice(0, 4); return React.createElement("div", { className: "qline" }, React.createElement("div", { className: "qline-ph ph", "data-label": "" }), React.createElement("div", { className: "qline-main" }, React.createElement(Link, { to: "/p/" + line.product_slug, className: "qline-name" }, line.name), React.createElement("div", { className: "qline-sku mono" }, line.sku), specs.length ? React.createElement("div", { className: "qline-specs" }, specs.map(([k, v]) => React.createElement("span", { key: k, className: "chip" }, v))) : null, inter.length ? React.createElement("div", { className: "qline-inter" }, "Crosses: ", React.createElement("span", { className: "mono" }, inter.join(", "))) : null ), React.createElement("div", { className: "qline-right" }, React.createElement(Stepper, { value: line.quantity, onChange: q => quote.setQty(line.variant_id, q) }), React.createElement("div", { className: "qline-price" }, line.price !== null && line.price !== undefined ? React.createElement("span", { className: "price-now", style: { fontFamily: "var(--display)", fontSize: 18 } }, money(line.price * line.quantity)) : React.createElement("span", { className: "chip badge-call", style: { whiteSpace: "nowrap" } }, "Quote pending"), line.core_charge ? React.createElement("div", { style: { fontSize: 11.5, color: "var(--ink-45)" } }, "+ " + money(line.core_charge * line.quantity) + " core") : null), React.createElement("button", { className: "qline-del", onClick: () => quote.remove(line.variant_id), "aria-label": "Remove" }, React.createElement(Icon, { name: "close", size: 16 })) ) ); } function CustomRow({ row }) { const quote = useQuote(); return React.createElement("div", { className: "qline qline-custom" }, React.createElement("div", { className: "qline-ph ph ph-custom", "data-label": "custom" }), React.createElement("div", { className: "qline-main" }, React.createElement("input", { className: "qcustom-input", placeholder: "Describe the part — e.g. pilot bearing for DD15, or paste a competitor number", value: row.description, onChange: e => quote.setCustomField(row._id, "description", e.target.value) }), React.createElement("div", { className: "qline-sku" }, "Custom line — we'll source it & price it") ), React.createElement("div", { className: "qline-right" }, React.createElement(Stepper, { value: row.quantity, onChange: q => quote.setCustomField(row._id, "quantity", q) }), React.createElement("span", { className: "chip badge-call", style: { whiteSpace: "nowrap" } }, "Quote pending"), React.createElement("button", { className: "qline-del", onClick: () => quote.removeCustom(row._id), "aria-label": "Remove" }, React.createElement(Icon, { name: "close", size: 16 })) ) ); } function QuotePage() { const quote = useQuote(); const { navigate } = useRouter(); const [contact, setContact] = usePersist("dwt_quote_contact", { email: "", name: "", phone: "", company: "", message: "" }); const [sending, setSending] = useState(false); const [done, setDone] = useState(null); // {quote_number} const [err, setErr] = useState(""); const priced = quote.items.filter(i => i.price !== null && i.price !== undefined); const subtotal = priced.reduce((s, i) => s + i.price * i.quantity, 0); const coreTotal = quote.items.reduce((s, i) => s + (i.core_charge || 0) * i.quantity, 0); const hasPending = quote.items.some(i => i.price === null || i.price === undefined) || quote.custom.length; const totalLines = quote.items.length + quote.custom.length; const setC = (k, v) => setContact(c => ({ ...c, [k]: v })); const submit = (e) => { e.preventDefault(); if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(contact.email)) { setErr("Enter a valid email so we can send your quote."); return; } if (totalLines === 0) { setErr("Add at least one part to your quote."); return; } setErr(""); setSending(true); const payload = { email: contact.email, name: contact.name || null, phone: contact.phone || null, company: contact.company || null, message: contact.message || null, items: quote.items.map(i => ({ variant_id: i.variant_id, quantity: i.quantity })), custom_items: quote.custom.filter(c => (c.description || "").trim()) .map(c => ({ description: c.description.trim(), quantity: c.quantity || 1 })), }; fetch((window.DATA && window.DATA.API_BASE ? window.DATA.API_BASE : "/api/v1") + "/quote/generate", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) .then(r => { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); }) .then(d => { setSending(false); setDone({ quote_number: d.quote_number }); window.scrollTo(0, 0); }) .catch(() => { setSending(false); setErr("Couldn't send the quote — please try again or call 410-235-8829."); }); }; if (done) return React.createElement(QuoteSuccess, { quoteNumber: done.quote_number, email: contact.email, onReset: () => { setDone(null); } }); return React.createElement("div", { className: "quote-page" }, React.createElement("div", { className: "quote-hero" }, React.createElement("div", { className: "wrap" }, React.createElement("span", { className: "eyebrow" }, "Flagship tool · No account needed"), React.createElement("h1", null, "Quote Builder"), React.createElement("p", null, "Assemble catalog parts and custom line items, then email yourself a formatted quote. Our shop gets a copy and follows up with pricing and freight."))), React.createElement("div", { className: "wrap quote-layout" }, React.createElement("div", { className: "quote-main" }, totalLines === 0 ? React.createElement("div", { className: "quote-empty" }, React.createElement(Icon, { name: "quote", size: 40, style: { color: "var(--ink-30)" } }), React.createElement("h3", null, "Your quote is empty"), React.createElement("p", { className: "muted" }, "Add parts from the catalog, or start with a custom line below."), React.createElement(Link, { to: "/c/heavy-duty-clutches", className: "btn btn-ink" }, "Browse the catalog")) : React.createElement(React.Fragment, null, React.createElement("div", { className: "between", style: { marginBottom: 14 } }, React.createElement("h2", { style: { fontSize: 20 } }, "Catalog parts (" + quote.items.length + ")"), quote.items.length ? React.createElement("button", { className: "linkbtn", onClick: quote.clear }, "Clear all") : null), quote.items.map(l => React.createElement(QuoteLine, { key: l.variant_id, line: l })), quote.custom.length ? React.createElement("h2", { style: { fontSize: 20, margin: "26px 0 14px" } }, "Custom parts") : null, quote.custom.map(r => React.createElement(CustomRow, { key: r._id, row: r })) ), React.createElement("button", { className: "add-custom", onClick: () => quote.addCustom({}) }, React.createElement(Icon, { name: "plus", size: 18 }), "Add a custom part") ), React.createElement("aside", { className: "quote-side" }, React.createElement("div", { className: "quote-card" }, React.createElement("h3", null, "Quote summary"), React.createElement("div", { className: "qsum-row" }, React.createElement("span", null, "Priced subtotal"), React.createElement("b", null, money(subtotal))), coreTotal ? React.createElement("div", { className: "qsum-row" }, React.createElement("span", null, "Core charges (refundable)"), React.createElement("b", null, money(coreTotal))) : null, hasPending ? React.createElement("div", { className: "qsum-pending" }, React.createElement(Icon, { name: "clock", size: 14 }), "Some items priced after review") : null, React.createElement("div", { className: "qsum-total" }, React.createElement("span", null, "Estimated total"), React.createElement("b", null, money(subtotal + coreTotal))), React.createElement("p", { className: "qsum-note" }, "Final pricing, freight and availability confirmed by our shop. Prices never computed in your browser.") ), React.createElement("form", { className: "quote-card contact-card", onSubmit: submit }, React.createElement("h3", null, "Email me this quote"), React.createElement("label", { className: "field" }, React.createElement("span", null, "Email ", React.createElement("em", null, "required")), React.createElement("input", { type: "email", value: contact.email, onChange: e => setC("email", e.target.value), placeholder: "you@company.com", required: true })), React.createElement("div", { className: "field-row" }, React.createElement("label", { className: "field" }, React.createElement("span", null, "Name"), React.createElement("input", { value: contact.name, onChange: e => setC("name", e.target.value), placeholder: "Optional" })), React.createElement("label", { className: "field" }, React.createElement("span", null, "Phone"), React.createElement("input", { value: contact.phone, onChange: e => setC("phone", e.target.value), placeholder: "Optional" }))), React.createElement("label", { className: "field" }, React.createElement("span", null, "Company"), React.createElement("input", { value: contact.company, onChange: e => setC("company", e.target.value), placeholder: "Optional" })), React.createElement("label", { className: "field" }, React.createElement("span", null, "Message"), React.createElement("textarea", { value: contact.message, onChange: e => setC("message", e.target.value), placeholder: "VIN, axle config, anything that helps us spec it", rows: 3 })), err ? React.createElement("div", { className: "field-err" }, err) : null, React.createElement("button", { type: "submit", className: "btn btn-purple btn-block btn-lg", disabled: sending }, sending ? "Sending…" : React.createElement(React.Fragment, null, React.createElement(Icon, { name: "mail", size: 18 }), "Email me this quote")), React.createElement("p", { className: "qsum-note", style: { textAlign: "center" } }, "Sends a copy to you and to ", React.createElement("b", null, "kevin@dwt.solutions")) ) ) ), React.createElement("div", { className: "sticky-bar quote-sticky" }, React.createElement("div", null, React.createElement("b", null, totalLines + " " + (totalLines === 1 ? "item" : "items")), React.createElement("div", { style: { fontSize: 12, color: "var(--ink-45)" } }, money(subtotal + coreTotal) + " est.")), React.createElement("button", { className: "btn btn-purple btn-lg", style: { flex: 1 }, onClick: () => document.querySelector(".contact-card input").scrollIntoView({ block: "center" }) }, "Get quote")) ); } function QuoteSuccess({ quoteNumber, email, onReset }) { const quote = useQuote(); useEffect(() => { /* keep items so the share view can render */ }, []); return React.createElement("div", { className: "wrap quote-success" }, React.createElement("div", { className: "success-card" }, React.createElement("div", { className: "success-ring" }, React.createElement(Icon, { name: "check", size: 34 })), React.createElement("h1", null, "Quote sent"), React.createElement("p", { className: "muted" }, "We emailed ", React.createElement("b", null, quoteNumber), " to ", React.createElement("b", null, email), " and to our shop. Kevin will follow up with pricing, freight and availability — usually same business day."), React.createElement("div", { className: "success-actions" }, React.createElement(Link, { to: "/quote/" + quoteNumber, className: "btn btn-ink btn-lg" }, "View shareable quote"), React.createElement(Link, { to: "/c/heavy-duty-clutches", className: "btn btn-ghost btn-lg", onClick: onReset }, "Keep shopping")), React.createElement("p", { className: "qsum-note", style: { marginTop: 18 } }, "Didn't get it? Check spam or call ", React.createElement("a", { href: "tel:4102358829", style: { color: "var(--blue)" } }, "410-235-8829")) ) ); } /* shareable read-only quote view: GET /quote/{number} */ function QuoteView({ number }) { const quote = useQuote(); const lines = quote.items; const subtotal = lines.filter(i => i.price != null).reduce((s, i) => s + i.price * i.quantity, 0); const coreTotal = lines.reduce((s, i) => s + (i.core_charge || 0) * i.quantity, 0); return React.createElement("div", { className: "wrap quote-view" }, React.createElement("div", { className: "qv-doc" }, React.createElement("div", { className: "qv-head" }, React.createElement("div", null, React.createElement(Logo, null), React.createElement("p", { className: "muted", style: { fontSize: 13, marginTop: 8 } }, "2601 Sisson St, Baltimore MD 21211 · 410-235-8829")), React.createElement("div", { style: { textAlign: "right" } }, React.createElement("div", { className: "eyebrow" }, "Quotation"), React.createElement("div", { style: { fontFamily: "var(--display)", fontSize: 24, fontWeight: 700 } }, number), React.createElement("div", { className: "muted", style: { fontSize: 13 } }, new Date().toLocaleDateString()))), React.createElement("table", { className: "qv-table" }, React.createElement("thead", null, React.createElement("tr", null, ["SKU", "Part", "Qty", "Unit", "Line"].map(h => React.createElement("th", { key: h }, h)))), React.createElement("tbody", null, lines.length === 0 ? React.createElement("tr", null, React.createElement("td", { colSpan: 5, style: { textAlign: "center", padding: 30, color: "var(--ink-45)" } }, "This quote's line items live on the server. (Prototype: add parts in the Quote Builder to preview.)")) : null, lines.map(l => React.createElement("tr", { key: l.variant_id }, React.createElement("td", { className: "mono" }, l.sku), React.createElement("td", null, l.name), React.createElement("td", null, l.quantity), React.createElement("td", null, l.price != null ? money(l.price) : "—"), React.createElement("td", null, l.price != null ? money(l.price * l.quantity) : React.createElement("span", { className: "chip badge-call" }, "Pending")))) )), lines.length ? React.createElement("div", { className: "qv-totals" }, React.createElement("div", { className: "qsum-row" }, React.createElement("span", null, "Subtotal (priced)"), React.createElement("b", null, money(subtotal))), coreTotal ? React.createElement("div", { className: "qsum-row" }, React.createElement("span", null, "Core charges"), React.createElement("b", null, money(coreTotal))) : null, React.createElement("div", { className: "qsum-total" }, React.createElement("span", null, "Estimated total"), React.createElement("b", null, money(subtotal + coreTotal)))) : null, React.createElement("p", { className: "qsum-note" }, "Valid 30 days. Final freight and any quote-only pricing confirmed by D&W Truck.") ) ); } Object.assign(window, { QuotePage, QuoteSuccess, QuoteView });