/* solarengineering.online — first-visit onboarding: account → verify → first project */ (function () { const { Icon, Btn, Badge } = window; const h = React.createElement; const { useState, useRef, useEffect } = React; const STEPS = [ { key: "account", n: "01", label: "Your account", sub: "Email & name" }, { key: "verify", n: "02", label: "Verify email", sub: "Six-digit code" }, ]; function Onboarding({ onComplete, onLogin }) { const [step, setStep] = useState(0); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [showLogin, setShowLogin] = useState(false); const [data, setData] = useState({ email: "", first: "", last: "", company: "", phone: "", password: "", }); const set = (k, v) => setData((d) => ({ ...d, [k]: v })); const idx = step; async function createAccount() { setBusy(true); setError(""); try { await window.API.auth.register( data.email, data.first, data.last, data.company, data.phone, data.password, ); setStep(1); } catch (e) { setError(e.message || "Could not create account"); } finally { setBusy(false); } } async function verifyEmail(code) { setBusy(true); setError(""); try { await window.API.auth.verifyEmail(code); onComplete(data); } catch (e) { setError(e.message || "Invalid verification code"); } finally { setBusy(false); } } if (showLogin) { return h(LoginScreen, { onLogin, onBack: () => { setShowLogin(false); setError(""); }, }); } return h( "div", { className: "onb" }, h(Aside, { step: idx }), h( "div", { className: "onb-main" }, h( "div", { className: "onb-main-top" }, h( "span", null, "Already have an account? ", h( "a", { href: "#", onClick: (e) => { e.preventDefault(); setShowLogin(true); }, }, "Sign in", ), ), h( Badge, { kind: "neutral", mono: true }, "Step " + (idx + 1) + " / " + STEPS.length, ), ), h( "div", { className: "onb-form-wrap" }, h( "div", { className: "onb-form fade-in", key: idx }, error && h( "div", { style: { color: "var(--signal-danger)", fontSize: 13, marginBottom: 12, }, }, error, ), idx === 0 && h(AccountStep, { data, set, busy, onNext: createAccount }), idx === 1 && h(VerifyStep, { data, busy, onBack: () => setStep(0), onNext: verifyEmail, }), ), ), ), ); } // ---------- Login screen ---------- function LoginScreen({ onLogin, onBack }) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); async function submit(e) { e && e.preventDefault(); if (!email || !password) return; setBusy(true); setError(""); try { await onLogin(email, password); } catch (err) { setError(err.message || "Invalid email or password"); setBusy(false); } } return h( "div", { className: "onb" }, h(Aside, { step: 0 }), h( "div", { className: "onb-main" }, h( "div", { className: "onb-main-top" }, h( "span", null, "New here? ", h( "a", { href: "#", onClick: (e) => { e.preventDefault(); onBack(); }, }, "Create account", ), ), ), h( "div", { className: "onb-form-wrap" }, h( "form", { className: "onb-form fade-in", onSubmit: submit }, h("div", { className: "eyebrow onb-eyebrow" }, "Welcome back"), h("h1", null, "Sign in"), h( "p", { className: "lead" }, "Enter your email and password to access your projects.", ), error && h( "div", { style: { color: "var(--signal-danger)", fontSize: 13, marginBottom: 12, padding: "10px 14px", background: "var(--bg-sunken)", borderRadius: "var(--radius-sm)", border: "1px solid var(--signal-danger)", }, }, error, ), h( "div", { className: "field" }, h("label", null, "Email"), h( "div", { className: "input-affix" }, h( "span", { className: "ic" }, h(Icon, { name: "mail", size: 16 }), ), h("input", { className: "input", type: "email", placeholder: "you@company.com", value: email, autoFocus: true, autoComplete: "email", onChange: (e) => setEmail(e.target.value), }), ), ), h( "div", { className: "field" }, h("label", null, "Password"), h( "div", { className: "input-affix" }, h( "span", { className: "ic" }, h(Icon, { name: "lock", size: 16 }), ), h("input", { className: "input", type: "password", placeholder: "••••••••", value: password, autoComplete: "current-password", onChange: (e) => setPassword(e.target.value), }), ), ), h( "div", { className: "onb-actions" }, h( Btn, { kind: "primary", block: true, iconRight: busy ? null : "arrow-right", disabled: !email || !password || busy, onClick: submit, }, busy ? "Signing in…" : "Sign in", ), ), ), ), ), ); } function Aside({ step }) { return h( "div", { className: "onb-aside" }, h( "div", { className: "onb-brand" }, h("img", { src: "/static/solarengineering-logo.svg", alt: "solarengineering.online", style: { height: 30, width: "auto", display: "block" }, }), ), h( "div", { className: "onb-aside-body" }, h("h2", null, "Structural Engineering for your PV Solar Projects."), h( "p", null, "Your time matters to us. Complete the structural engineering for your residential Solar Roof mount, ground mount, and ESS project in just 5–10 minutes, with results you can trust.", ), ), h( "div", { className: "onb-prog" }, STEPS.map((s, i) => { const state = i < step ? "done" : i === step ? "current" : "pending"; return h( "div", { key: s.key, className: "onb-pstep " + state }, h( "div", { className: "onb-pnode" }, h( "div", { className: "onb-pdot" }, i < step ? h(Icon, { name: "check", size: 13 }) : s.n, ), h("div", { className: "onb-pline" }), ), h( "div", { className: "onb-plabel" }, h("div", { className: "pl" }, s.label), h("div", { className: "ps" }, s.sub), ), ); }), ), ); } // ---------- Step 1: account ---------- const US_PHONE_RE = /^\+?1?\s*[\-.]?\s*\(?\d{3}\)?[\s\-.]?\d{3}[\s\-.]?\d{4}$/; function AccountStep({ data, set, busy, onNext }) { const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email); const phoneValid = US_PHONE_RE.test(data.phone.trim()); const passwordValid = (data.password || "").length >= 8; const ready = emailValid && data.first.trim() && data.last.trim() && data.company.trim() && phoneValid && passwordValid; return h( "div", null, h("div", { className: "eyebrow onb-eyebrow" }, "Create your account"), h("h1", null, "Let's get you set up"), h( "p", { className: "lead" }, "A few details so we can attach your projects, invoices, and deliverables to the right company account.", ), h( "div", { className: "field" }, h("label", null, "Work email ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" + (emailValid ? " valid" : "") }, h("span", { className: "ic" }, h(Icon, { name: "mail", size: 16 })), h("input", { className: "input", type: "email", placeholder: "you@company.com", value: data.email, onChange: (e) => set("email", e.target.value), }), h( "span", { className: "ok" }, h(Icon, { name: "check-circle", size: 16 }), ), ), ), h( "div", { className: "onb-row2" }, h( "div", { className: "field" }, h("label", null, "First name ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" }, h("span", { className: "ic" }, h(Icon, { name: "user", size: 16 })), h("input", { className: "input", placeholder: "John", value: data.first, onChange: (e) => set("first", e.target.value), }), ), ), h( "div", { className: "field" }, h("label", null, "Last name ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" }, h("span", { className: "ic" }, h(Icon, { name: "user", size: 16 })), h("input", { className: "input", placeholder: "McCarthy", value: data.last, onChange: (e) => set("last", e.target.value), }), ), ), ), h( "div", { className: "field" }, h("label", null, "Company ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" }, h( "span", { className: "ic" }, h(Icon, { name: "building", size: 16 }), ), h("input", { className: "input", placeholder: "Solar and Battery Systems Inc.", value: data.company, onChange: (e) => set("company", e.target.value), }), ), ), h( "div", { className: "field" }, h("label", null, "Phone ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" + (data.phone && phoneValid ? " valid" : ""), }, h("span", { className: "ic" }, h(Icon, { name: "phone", size: 16 })), h("input", { className: "input", type: "tel", placeholder: "(555) 555-5555", value: data.phone, onChange: (e) => set("phone", e.target.value), }), h( "span", { className: "ok" }, h(Icon, { name: "check-circle", size: 16 }), ), ), data.phone && !phoneValid && h( "div", { style: { color: "var(--signal-danger)", fontSize: 12, marginTop: 4, }, }, "Enter a valid US phone number", ), ), h( "div", { className: "field" }, h("label", null, "Password ", h("span", { className: "req" }, "*")), h( "div", { className: "input-affix" + (passwordValid ? " valid" : "") }, h("span", { className: "ic" }, h(Icon, { name: "lock", size: 16 })), h("input", { className: "input", type: "password", autoComplete: "new-password", placeholder: "At least 8 characters", value: data.password, onChange: (e) => set("password", e.target.value), }), h( "span", { className: "ok" }, h(Icon, { name: "check-circle", size: 16 }), ), ), ), h( "div", { className: "onb-actions" }, h( Btn, { kind: "primary", block: true, iconRight: busy ? null : "arrow-right", disabled: !ready || busy, onClick: onNext, }, busy ? "Creating..." : "Create account", ), ), h( "div", { className: "onb-legal" }, "By continuing you agree to the ", h("a", { href: "/terms", target: "_blank", rel: "noopener noreferrer" }, "Terms"), " and ", h( "a", { href: "/privacy", target: "_blank", rel: "noopener noreferrer" }, "Privacy Policy", ), ".", ), ); } // ---------- Step 2: verify ---------- function VerifyStep({ data, busy, onBack, onNext }) { const [code, setCode] = useState(["", "", "", "", "", ""]); const [resent, setResent] = useState(false); const refs = useRef([]); const filled = code.every((c) => c !== ""); useEffect(() => { if (refs.current[0]) refs.current[0].focus(); }, []); function onKey(i, e) { if (e.key === "Backspace" && !code[i] && i > 0) refs.current[i - 1].focus(); } function onChange(i, v) { v = v.replace(/\D/g, "").slice(-1); const next = code.slice(); next[i] = v; setCode(next); if (v && i < 5) refs.current[i + 1].focus(); } function resend(e) { e.preventDefault(); setResent(true); } return h( "div", null, h("div", { className: "eyebrow onb-eyebrow" }, "Verify your email"), h("h1", null, "Check your inbox"), h( "p", { className: "lead" }, "We sent a six-digit code to ", h("b", null, data.email || "your email"), ". Enter it to confirm the address.", ), h( "div", { className: "code-row" }, code.map((c, i) => h("input", { key: i, ref: (el) => (refs.current[i] = el), className: "code-cell" + (c ? " filled" : ""), inputMode: "numeric", maxLength: 1, value: c, onChange: (e) => onChange(i, e.target.value), onKeyDown: (e) => onKey(i, e), }), ), ), h( "div", { className: "resend" }, resent ? "Use the newest code from your inbox." : h( React.Fragment, null, "Didn't get it? ", h("a", { href: "#", onClick: resend }, "Check again"), ), ), h( "div", { className: "onb-actions" }, h(Btn, { kind: "ghost", icon: "arrow-left", onClick: onBack }, "Back"), h("span", { className: "grow" }), h( Btn, { kind: "primary", iconRight: busy ? null : "arrow-right", disabled: !filled || busy, onClick: () => onNext(code.join("")), }, busy ? "Verifying..." : "Verify & continue", ), ), ); } Object.assign(window, { Onboarding }); })();