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 (
);
}
/* --------------------
Tests (preserve existing + add one)
-------------------- */
export function runTests() {
const tests = [
{ desc: '1 couch' },
{ desc: '2 couches and 5 boxes' },
{ desc: 'old fridge and 3 boxes' },
{ desc: 'garage full of boxes' },
{ desc: 'hot tub, mattress, 10 bags' },
{ desc: '3 mattresses and 1 fridge (stairs)', opts: { stairs: true } },
{ desc: 'sofa, tv, 4 boxes' },
{ desc: 'sectional x2, 6 bags' },
];
if (typeof console !== 'undefined') {
console.group && console.group('JunkEstimator Tests');
tests.forEach(t => {
const r = estimateFromDescription(t.desc, t.opts || {});
console.log(`Test: "${t.desc}" => $${r.estimateLow} - $${r.estimateHigh} | vol: ${r.volume.toFixed(1)} yd³ | items: ${r.items.map(i => `${i.qty} ${i.key}`).join(', ')}`);
});
console.groupEnd && console.groupEnd();
}
}
// Auto-run tests in Node
if (typeof window === 'undefined' && typeof require !== 'undefined' && require.main === module) {
runTests();
}