/* QuoteFlow Prototype · Customer Portal. Where a customer accepts their proposal and pays the deposit. Reads/writes the same window.QFStore the admin sends proposals into. */ function PortalApp({ onGoAdmin }) { const { Card, Button, Badge, StatusBadge, Input, Avatar } = window.QuoteFlowDesignSystem_41788d; const store = useStore(); const [leadId, setLeadId] = React.useState(null); const [pay, setPay] = React.useState(false); // showing the payment form const [card, setCard] = React.useState({ name: '', num: '', exp: '', cvc: '' }); const settings = store.getSettings(); const lead = leadId ? store.getLead(leadId) : null; // ---------- account chooser (stand-in for a magic-link sign-in) ---------- if (!lead) { const leads = store.getLeads(); return (

Greenline Energy · Customer portal

Demo sign-in — choose a customer account to open their portal. In production each customer gets a secure magic link by email.

{leads.map((l, i) => ( ))}
); } const p = lead.proposal; const steps = store.portalSteps(lead); const paid = !!(p && p.payment); const Header = () => (
Greenline Energy
); // ---------- status tracker ---------- const Tracker = () => (
{steps.map((s, i) => (
{s.done ? : {i + 1}} {s.label} {s.meta && {s.meta}}
{i < steps.length - 1 && }
))}
); // ---------- action panel (varies by proposal state) ---------- const cardValid = card.name && card.num.replace(/\s/g, '').length >= 12 && card.exp && card.cvc.length >= 3; const ActionPanel = () => { if (!p) { return (
Your proposal is being prepared
Greenline Energy is finalising your quote — you'll get an email when it's ready to review.
); } if (paid) { return (
You're all set — deposit paid
We've received your £{p.payment.amount.toLocaleString('en-GB')} deposit (card ····{p.payment.last4}). Greenline will call to book your survey.
); } if (pay) { return (

Pay your deposit

Secure · demo
15% deposit due now {gbp(p.deposit)}
setCard(Object.assign({}, card, { name: e.target.value }))} /> setCard(Object.assign({}, card, { num: e.target.value }))} />
setCard(Object.assign({}, card, { exp: e.target.value }))} style={{ flex: 1 }} /> setCard(Object.assign({}, card, { cvc: e.target.value }))} style={{ flex: 1 }} />

Demo only — no card is charged. Use any numbers.

); } // proposal sent or accepted (not yet paid) const accepted = p.status === 'accepted'; return (

{accepted ? 'Proposal accepted' : 'Review your proposal'}

{accepted ? 'Thanks for accepting! Pay your deposit below to secure your install slot.' : 'Greenline Energy has prepared your proposal. Accept it to move forward — then pay a small deposit to book your survey.'}

{!accepted && } {(accepted || !settings.takeDeposits) && settings.takeDeposits && }
{!settings.takeDeposits && accepted &&

No deposit required — Greenline will be in touch to book your survey.

}
); }; return (

Hello {lead.name.split(' ')[0]} 👋

Track your {lead.system} project and complete the next step below.

{/* booked appointments */} {lead.project && (lead.project.surveyDate || lead.project.installDate) && (

Your appointments

{lead.project.surveyDate && (
Technical survey
{store.prettyISO(lead.project.surveyDate)}{lead.project.completedAt ? '' : ' · we\u2019ll confirm a time slot'}
)} {lead.project.installDate && (
Installation
{lead.project.completedAt ? 'Completed — thank you!' : store.prettyISO(lead.project.installDate)}
)}
)} {/* proposal / quote summary */}

{p ? 'Your proposal' : 'Your saved quote'}

{(p ? p.items : [{ label: lead.system, sub: 'Estimated', amount: lead.value }]).map((it, i, arr) => (
{it.label}
{it.sub}
{gbp(it.amount)}
))}
{p ? 'Total' : 'Estimated range'} {p ? gbp(p.total) : gbp(lead.low) + ' – ' + gbp(lead.high)}
{/* documents + contact */}

Documents

{[['Your quote summary', 'file-text'], ['Proposal' + (p ? '' : ' (pending)'), 'file-check'], ['Terms & conditions', 'scroll-text']].map((d, i) => (
-1 ? 'var(--text-faint)' : 'var(--text-body)') }}> {d[0]} {d[0].indexOf('pending') === -1 && }
))}

Your installer

{lead.owner === 'Unassigned' ? 'Greenline Energy' : lead.owner}
01305 123 456
); } window.PortalApp = PortalApp;