import React, { useMemo, useState } from "react"; /** * Ai Junk Removal Estimator (React) — fixed implementation * * Problems fixed in this update: * - `estimateFromDescription` was missing (left as a placeholder). Tests and the * preview depended on it, causing runtime errors. Implemented it fully. * - Removed the broken/truncated base64 approach and replaced it with a * small, safe Squarespace snippet string that avoids fragile escape sequences. * * This file preserves existing test cases and adds one additional test. */ /* -------------------- Simple Squarespace snippet (safe, editable) -------------------- */ export const squarespaceSnippet = `
`; /* -------------------- Estimation logic (complete implementation) -------------------- */ function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); } function escapeForRegex(s) { return String(s).replace(/[.*+?^${}()|[\\]\\]/g, "\\$&"); } export function estimateFromDescription(description, options = {}) { const CONFIG_LOCAL = { truckCapacityYd3: 15, baseTiers: [ { max: 2, price: 150 }, { max: 4, price: 250 }, { max: 7, price: 400 }, { max: 11, price: 550 }, { max: 15, price: 700 } ], items: { couch: { volume: 3 }, sofa: { volume: 3 }, sectional: { volume: 4 }, loveseat: { volume: 2 }, recliner: { volume: 1.2 }, chair: { volume: 0.6 }, mattress: { volume: 2, surcharge: 30 }, "box spring": { volume: 1.2 }, bed: { volume: 2 }, dresser: { volume: 2 }, nightstand: { volume: 0.7 }, table: { volume: 1.5 }, desk: { volume: 1.5 }, bookshelf: { volume: 1.2 }, rug: { volume: 0.6 }, tv: { volume: 0.5, surcharge: 20 }, television: { volume: 0.5, surcharge: 20 }, refrigerator: { volume: 3, surcharge: 50 }, fridge: { volume: 3, surcharge: 50 }, washer: { volume: 2.2, surcharge: 35 }, dryer: { volume: 2.2, surcharge: 35 }, stove: { volume: 2.2, surcharge: 35 }, piano: { volume: 5, surcharge: 75 }, grill: { volume: 1.5 }, treadmill: { volume: 2.5 }, "hot tub": { volume: 10, surcharge: 150 }, bag: { volume: 0.12 }, boxes: { volume: 0.15 } }, fees: { stairs: 30, sameDay: 25, heavy: 40, curbsideDiscount: -20 }, hazmatKeywords: ["paint","chemicals","oil","gasoline","propane","tires","batteries","asbestos"] }; const text = ` ${String(description || "").toLowerCase()} `; let totalVolume = 0; let surcharges = 0; const details = []; // process longer keys first to avoid partial matches const itemKeys = Object.keys(CONFIG_LOCAL.items).sort((a,b)=>b.length-a.length); for (const key of itemKeys) { const spec = CONFIG_LOCAL.items[key]; const escaped = escapeForRegex(key); const patterns = [ new RegExp('(?:^|[^a-zA-Z0-9])([0-9]+)[ \t]*(?:x|×)?[ \t]*'+escaped+'s?(?:[^a-zA-Z0-9]|$)','gi'), new RegExp('(?:x|×)[ \t]*([0-9]+)[ \t]*'+escaped+'s?(?:[^a-zA-Z0-9]|$)','gi'), new RegExp(escaped+'s?[ \t]*\\((\\d+)\\)','gi'), new RegExp('\\b(one|two|three|four|five|six|seven|eight|nine|ten)[ \t]+'+escaped+'s?\\b','gi'), new RegExp('(?:^|[^a-zA-Z0-9])'+escaped+'s?(?:[^a-zA-Z0-9]|$)','gi') ]; let qty = 0; for (const re of patterns) { let m; while ((m = re.exec(text)) !== null) { if (m[1]) { const val = isNaN(Number(m[1])) ? (("one two three four five six seven eight nine ten").split(" ").indexOf(m[1].toLowerCase()) + 1) || 1 : Number(m[1]); qty += val; } else { qty += 1; } } } if (qty > 0) { const vol = (spec.volume || 0) * qty; const fee = (spec.surcharge || 0) * qty; totalVolume += vol; surcharges += fee; details.push({ key, qty, vol, fee }); } } if (/garage|attic|basement|storage unit/.test(text)) totalVolume += 1.5; if (/construction|renovation|remodel|demolition|tile|concrete/.test(text)) surcharges += CONFIG_LOCAL.fees.heavy; const haz = CONFIG_LOCAL.hazmatKeywords.filter(k => text.includes(k)); const priceBase = (() => { const t = CONFIG_LOCAL.baseTiers.find(t => totalVolume <= t.max) || CONFIG_LOCAL.baseTiers[CONFIG_LOCAL.baseTiers.length-1]; return t.price; })(); let fees = surcharges; if (options.stairs) fees += CONFIG_LOCAL.fees.stairs; if (options.sameDay) fees += CONFIG_LOCAL.fees.sameDay; if (options.curbside) fees += CONFIG_LOCAL.fees.curbsideDiscount; const subtotal = clamp(priceBase + fees, 75, 5000); const low = Math.max(75, Math.round(subtotal * 0.9)); const high = Math.round(subtotal * 1.1); return { volume: Number(totalVolume.toFixed(1)), priceBase, fees, feesBreakdown: { ...(options.stairs ? { stairs: CONFIG_LOCAL.fees.stairs } : {}), ...(options.sameDay ? { sameDay: CONFIG_LOCAL.fees.sameDay } : {}), ...(options.curbside ? { curbside: CONFIG_LOCAL.fees.curbsideDiscount } : {}), surcharges }, hazmat: haz, items: details, estimateLow: low, estimateHigh: high }; } /* -------------------- React preview component -------------------- */ export default function JunkEstimatorPreview() { const [description, setDescription] = useState(""); const [stairs, setStairs] = useState(false); const [sameDay, setSameDay] = useState(false); const [curbside, setCurbside] = useState(false); const result = useMemo(() => estimateFromDescription(description, { stairs, sameDay, curbside }), [description, stairs, sameDay, curbside]); const doCopy = async () => { try { await navigator.clipboard.writeText(squarespaceSnippet); alert("Squarespace snippet copied to clipboard — paste into a Code Block on Squarespace."); } catch (e) { prompt("Copy the Squarespace snippet (Ctrl/Cmd+C, Enter):", squarespaceSnippet); } }; return (

Preview

© 2025 Mr. Bulk LLC. All rights reserved.