import { useState, useRef } from "react";
const gold = "#c9a96e";
const goldDark = "#a67c4a";
const bg = "#0f0f0f";
const card = "#181818";
const border = "#252525";
const textPrimary = "#f0ede6";
const textMuted = "#777";
const textDim = "#444";
const STEPS = [
{ id: "business", label: "Business", icon: "🏢" },
{ id: "client", label: "Client", icon: "👤" },
{ id: "scope", label: "Scope", icon: "📋" },
{ id: "pricing", label: "Pricing", icon: "💰" },
{ id: "generate", label: "Send", icon: "✨" },
];
const PROJECT_TYPES = [
"Kitchen Remodel","Bathroom Remodel","Full Home Renovation",
"Addition / Extension","Basement Finish","Deck / Outdoor Living",
"Custom Home Build","Commercial TI","Interior Design",
];
const DURATIONS = [
"1–2 weeks","3–4 weeks","5–6 weeks","6–8 weeks",
"2–3 months","3–4 months","4–6 months","6–12 months",
];
// Big mobile-friendly input
function MInput({ label, hint, optional, type="text", ...props }) {
const [focused, setFocused] = useState(false);
return (
);
}
function MTextarea({ label, hint, optional, ...props }) {
const [focused, setFocused] = useState(false);
return (
);
}
// Pill-button selector (replaces dropdowns for short lists)
function PillSelect({ label, options, value, onChange }) {
return (
);
}
// Native select for long lists
function MSelect({ label, optional, children, ...props }) {
const [focused, setFocused] = useState(false);
return (
);
}
export default function App() {
const [step, setStep] = useState(0);
const [logoUrl, setLogoUrl] = useState(null);
const logoInputRef = useRef();
const [proposal, setProposal] = useState("");
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);
const [shared, setShared] = useState(false);
const [form, setForm] = useState({
contractorName: "", contractorTitle: "", contractorCompany: "",
contractorPhone: "", contractorEmail: "", contractorWebsite: "",
contractorAddress: "", contractorLicense: "", contractorInsurance: "",
clientName: "", clientEmail: "", clientPhone: "",
projectType: "Kitchen Remodel", projectAddress: "",
scopeDescription: "", startDate: "", duration: "",
totalPrice: "", depositPercent: "30", paymentSchedule: "milestone", warranty: "1 year",
});
const u = (k, v) => setForm(f => ({ ...f, [k]: v }));
const handleLogoUpload = (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => setLogoUrl(ev.target.result);
reader.readAsDataURL(file);
};
const canProceed = () => {
if (step === 0) return form.contractorName && form.contractorCompany && form.contractorPhone && form.contractorEmail;
if (step === 1) return form.clientName && form.projectAddress;
if (step === 2) return form.scopeDescription && form.duration;
if (step === 3) return form.totalPrice;
return true;
};
const generateProposal = async () => {
setLoading(true);
setProposal("");
try {
const prompt = `You are an expert construction proposal writer. Generate a professional, detailed project proposal.
CONTRACTOR: ${form.contractorName}${form.contractorTitle ? `, ${form.contractorTitle}` : ""} | ${form.contractorCompany}
Phone: ${form.contractorPhone} | Email: ${form.contractorEmail}${form.contractorWebsite ? ` | ${form.contractorWebsite}` : ""}
${form.contractorAddress ? `Address: ${form.contractorAddress}` : ""}
${form.contractorLicense ? `License: ${form.contractorLicense}` : ""}
${form.contractorInsurance ? `Insurance: ${form.contractorInsurance}` : ""}
CLIENT: ${form.clientName}${form.clientEmail ? ` | ${form.clientEmail}` : ""}${form.clientPhone ? ` | ${form.clientPhone}` : ""}
PROJECT: ${form.projectType} at ${form.projectAddress}
Scope: ${form.scopeDescription}
${form.startDate ? `Start: ${form.startDate}` : ""} | Duration: ${form.duration}
Total: $${parseInt(form.totalPrice).toLocaleString()} | Deposit: ${form.depositPercent}% | Payment: ${form.paymentSchedule} | Warranty: ${form.warranty}
Write a complete professional proposal:
1. Cover Letter (warm, 2–3 sentences to client)
2. Project Overview
3. Scope of Work (bullet points)
4. Timeline
5. Investment Summary (show deposit $amount and remaining balance)
6. Terms & Conditions (4 points)
7. Warranty
8. Signature Block (lines for both parties)
Premium, trustworthy tone. Include license/insurance where relevant.`;
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"anthropic-dangerous-direct-browser-access": "true",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1500,
messages: [{ role: "user", content: prompt }],
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
setProposal(`Error ${res.status}: ${err?.error?.message || res.statusText}`);
setLoading(false);
return;
}
const data = await res.json();
setProposal(data.content?.map(b => b.text || "").join("") || "No content returned.");
} catch (e) {
setProposal(`Error: ${e.message}`);
}
setLoading(false);
};
const handleShare = async () => {
if (navigator.share) {
try {
await navigator.share({ title: `Proposal for ${form.clientName}`, text: proposal });
setShared(true);
setTimeout(() => setShared(false), 2000);
} catch {}
} else {
navigator.clipboard.writeText(proposal);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const deposit = form.totalPrice ? Math.round(form.totalPrice * form.depositPercent / 100) : 0;
const remaining = form.totalPrice ? Math.round(form.totalPrice * (1 - form.depositPercent / 100)) : 0;
return (
{optional && optional}
{hint && {hint}
} setFocused(true)} onBlur={() => setFocused(false)} />
{optional && optional}
{hint && {hint}
}
{options.map(o => {
const val = typeof o === "object" ? o.value : o;
const lbl = typeof o === "object" ? o.label : o;
const active = value === val;
return (
);
})}
{optional && optional}
▼
{/* ── TOP BAR ── */}
{/* ── CONTENT ── */}
);
}
{logoUrl
?
: 🏗️}
{/* Step dots */}
BuildProposal AI
{form.contractorCompany || "Proposal Generator"}
{STEPS.map((s, i) => (
))}
{i < step ? "✓" : s.icon}
{i < STEPS.length - 1 && (
)}
Step {step + 1} of {STEPS.length} — {STEPS[step].label}
{/* STEP 0 — Business */}
{step === 0 && (
u("contractorName", e.target.value)} />
u("contractorTitle", e.target.value)} optional />
u("contractorCompany", e.target.value)} />
u("contractorPhone", e.target.value)} />
u("contractorEmail", e.target.value)} />
u("contractorWebsite", e.target.value)} optional />
u("contractorAddress", e.target.value)} optional />
u("contractorLicense", e.target.value)} optional hint="Adds credibility — shown on proposal" />
u("contractorInsurance", e.target.value)} optional />
)}
{/* STEP 1 — Client */}
{step === 1 && (
u("clientName", e.target.value)} />
u("clientEmail", e.target.value)} optional />
u("clientPhone", e.target.value)} optional />
u("projectType", e.target.value)}>
{PROJECT_TYPES.map(t => )}
u("projectAddress", e.target.value)} hint="Where the work is happening" />
)}
{/* STEP 2 — Scope */}
{step === 2 && (
u("scopeDescription", e.target.value)}
/>
u("startDate", e.target.value)} optional />
u("duration", v)}
/>
)}
{/* STEP 3 — Pricing */}
{step === 3 && (
u("totalPrice", e.target.value)} />
({ label: p, value: p.replace("%","") }))}
value={form.depositPercent}
onChange={v => u("depositPercent", v)}
/>
u("paymentSchedule", v)}
/>
u("warranty", v)}
/>
{/* Live payment summary */}
{form.totalPrice && (
)}
)}
{/* STEP 4 — Generate */}
{step === 4 && (
{/* ── STICKY BOTTOM NAV ── */}
Your business
Set this once — it'll appear on every proposal.
{/* Logo upload */} logoInputRef.current.click()} style={{
border: `2px dashed ${logoUrl ? gold : border}`, borderRadius: 16,
padding: 20, marginBottom: 20, display: "flex", alignItems: "center", gap: 16,
cursor: "pointer", background: "#111", transition: "border-color 0.2s",
}}>
{logoUrl
?
:
🖼️
}
{logoUrl ? "Logo uploaded ✓" : "Upload company logo"}
PNG or JPG · Tap to {logoUrl ? "change" : "add"}
Client & project
Who are you building the proposal for?
Scope & timeline
Bullet points are fine — AI will expand it.
Price & payment
Set the numbers — proposal does the math.
Payment Summary
{[
["Deposit at signing", `$${deposit.toLocaleString()}`],
["Remaining balance", `$${remaining.toLocaleString()}`],
["Total investment", `$${parseInt(form.totalPrice).toLocaleString()}`],
].map(([l, v]) => (
{l}
{v}
))}
{/* Proposal header card */}
)}
{loading && (
)}
{proposal && (
)}
)}
{logoUrl
?
:
🏗️
}
{form.contractorCompany}
{form.contractorPhone}
For
{form.clientName}
Your proposal
Ready in seconds. Review and send.
{!proposal && !loading && (📄
Tap "Generate" to build your proposal
Writing your proposal…
Usually takes 10–15 seconds
{proposal}
{step > 0 && (
)}
{step < 4 ? (
) : (
)}