// Kayser v2 — Onboarding flow (Google-first) // 6 screens: welcome → sign in (Google or manual) → tell me about you → what else // → Kayser folder in Drive → first find // Lives as a full-screen overlay above the app. // ============ INDUSTRY OPTIONS ============ const KXV2_INDUSTRIES = [ { id: 'foundation', label: 'Foundation repair', emoji: '🏠' }, { id: 'plumbing', label: 'Plumbing', emoji: '🔧' }, { id: 'hvac', label: 'HVAC', emoji: '❄️' }, { id: 'roofing', label: 'Roofing', emoji: '🏗️' }, { id: 'electric', label: 'Electrical', emoji: '⚡' }, { id: 'general', label: 'General contractor', emoji: '🧱' }, { id: 'cleaning', label: 'Cleaning services', emoji: '🧹' }, { id: 'landscape', label: 'Landscaping', emoji: '🌳' }, { id: 'lawyer', label: 'Solo law practice', emoji: '⚖️' }, { id: 'realtor', label: 'Real estate agent', emoji: '🔑' }, { id: 'accountant', label: 'Accountant / CPA', emoji: '📊' }, { id: 'consultant', label: 'Consultant / coach', emoji: '💼' }, { id: 'therapist', label: 'Therapist / health practitioner', emoji: '🧠' }, { id: 'other', label: 'Something else', emoji: '✦' }, ]; // ============ MAIN ONBOARDING ============ function KxV2Onboarding({ onComplete, onSkip }) { const [step, setStep] = React.useState(0); const [data, setData] = React.useState({ google: false, // signed in with Google? manual: null, // { email, imap, calendarUrl } when not Google name: '', business: '', industry: null, industryOther: '', money: 'sheets', // sheets (default if Google) | qb | xero | fresh | wave | track-with-kayser | skip tasks: 'kayser', // kayser | google | external (string) | skip docs: 'drive', // drive | dropbox | external | kayser }); const totalSteps = 6; const goNext = () => setStep(s => Math.min(s + 1, totalSteps - 1)); const goBack = () => setStep(s => Math.max(s - 1, 0)); return (
{/* Header */}
{step > 0 && step < totalSteps - 1 && ( )} {step > 0 && step < totalSteps - 1 && ( )}
{/* Step content */}
{step === 0 && } {step === 1 && } {step === 2 && } {step === 3 && } {step === 4 && } {step === 5 && }
); } function ProgressDots({ count, active }) { return (
{[...Array(count)].map((_, i) => (
))}
); } // ============ STEP 0 · Welcome ============ function StepWelcome({ onNext }) { return (
K
I'm Kayser.
Think of me as a second set of hands for your business — I read what comes in, draft what needs to go out, and handle the routine so you don't have to.
{[ { t: 'I start out asking before I do anything.', s: 'You stay in control.' }, { t: 'The more you approve, the more I do on my own.', s: "I'll never auto-handle something you haven't shown me first." }, { t: 'Three minutes to set up. You can change anything later.', s: '' }, ].map((r, i) => (
{i + 1}
{r.t}
{r.s &&
{r.s}
}
))}
); } // ============ STEP 1 · Sign in (Google or manual) ============ function StepSignIn({ onNext, data, setData }) { const [manualOpen, setManualOpen] = React.useState(false); const [manualData, setManualData] = React.useState({ email: '', imap: 'gmail', calendarUrl: '' }); const signInGoogle = () => { // Real flow: redirect to OAuth. Here we fake success. setData(d => ({ ...d, google: true, manual: null })); setTimeout(onNext, 250); }; const finishManual = () => { setData(d => ({ ...d, google: false, manual: manualData })); setTimeout(onNext, 100); }; return (
Connect your work life.
Almost everything I do works best through Google — email, calendar, docs, sheets, drive — all in one sign-in. It's the fastest way to get started.
{/* Big Google button */} {/* What it grants */}
One sign-in connects:
{[ { i: 'M', label: 'Gmail', sub: 'I read inbound, draft replies' }, { i: 'C', label: 'Calendar', sub: 'I watch for conflicts' }, { i: 'D', label: 'Drive', sub: 'I save things you own' }, { i: 'S', label: 'Sheets', sub: 'Books, customers, tasks' }, { i: 'X', label: 'Docs', sub: 'Notes, contracts, quotes' }, { i: '+', label: 'Forms', sub: 'Catch web form leads' }, ].map(x => (
{x.i}
{x.label}
{x.sub}
))}
{/* Trust note */}
Everything I write lives in your Drive, in your account, in formats you own — Sheets, Docs, files. If you ever stop using Kayser, your data stays put.
{/* Manual escape hatch */}
{manualOpen && (
That's okay — I can work with what you have.
Connect your email (any provider) and calendar separately. Some Google-specific features (Sheets-based books, Docs-based notes) will be limited until you connect one.
setManualData(d => ({ ...d, email: v }))} />
Email provider
{['gmail', 'outlook', 'other (IMAP)'].map(p => ( ))}
setManualData(d => ({ ...d, calendarUrl: v }))} />
)}
); } function GoogleG({ size = 20 }) { return ( ); } // ============ STEP 2 · Tell me about you ============ function StepTellMe({ onNext, data, setData }) { const set = (k, v) => setData(d => ({ ...d, [k]: v })); const isOther = data.industry === 'other'; const ready = data.name.trim() && data.business.trim() && data.industry && (!isOther || data.industryOther.trim()); return (
Tell me about you.
I'll use this to talk like a real coworker — using the words you actually use.
set('name', v)} /> set('business', v)} />
What do you do?
{KXV2_INDUSTRIES.map(i => ( ))}
{isOther && (
set('industryOther', v)} />
I'll ask a few quick questions in chat once we're done so I get your vocabulary right.
)}
); } // ============ STEP 3 · What else (money / tasks / docs / SMS) ============ function StepWhatElse({ onNext, data, setData }) { const isGoogle = data.google; const set = (k, v) => setData(d => ({ ...d, [k]: v })); // Money options vary slightly based on Google connection const moneyOptions = [ isGoogle && { id: 'sheets', label: "Track in a Kayser-managed Sheet", sub: "Auto-maintained P&L in your Google Sheets", recommended: true }, { id: 'qb', label: 'QuickBooks', sub: 'Connect existing books' }, { id: 'xero', label: 'Xero', sub: 'Connect existing books' }, { id: 'fresh', label: 'FreshBooks', sub: 'Connect existing books' }, { id: 'track-with-kayser', label: "I don't track money yet", sub: "Use Kayser's built-in basic accounting" }, { id: 'skip', label: "I'll handle this later", sub: "" }, ].filter(Boolean); const tasksOptions = [ { id: 'kayser', label: "Use Kayser's built-in to-do", sub: 'Simple list, syncs with chat' }, isGoogle && { id: 'google', label: 'Google Tasks', sub: 'Already connected' }, { id: 'external-todoist', label: 'Todoist', sub: 'Connect later' }, { id: 'external-notion', label: 'Notion / Things / other', sub: "I'll forward task mentions to you" }, ].filter(Boolean); const docsOptions = [ isGoogle && { id: 'drive', label: 'Google Drive', sub: 'Already connected', recommended: true }, { id: 'kayser', label: "Use Kayser's built-in files", sub: 'Simple folder, syncs with chat' }, { id: 'dropbox', label: 'Dropbox', sub: 'Connect later' }, { id: 'external', label: 'Notion / OneDrive / other', sub: "I'll forward what I make to you" }, ].filter(Boolean); return (
{isGoogle ? "A few more details." : "Anything else I should know?"}
{isGoogle ? "I already have your email and calendar via Google. These help me handle the rest." : "Tell me where you keep things. Each one I see gives me more to work with."}
{/* Phone — coming soon */}
📱
Phone & SMS
I'll provision a number for you once you're set up. Coming after launch.
SOON
{/* Money */} set('money', v)} /> {/* Tasks */} set('tasks', v)} /> {/* Docs */} set('docs', v)} />
Using something I don't list?
Forward emails to automations@kayser.com and I'll parse them. Works with any CRM, scheduler, or quoting tool.
); } function OnbPicker({ label, options, value, onChange }) { return (
{label}
{options.map(o => ( ))}
); } function OnbInput({ label, placeholder, value, onChange }) { return (
{label}
onChange(e.target.value)} placeholder={placeholder} style={{ width: '100%', boxSizing: 'border-box', padding: '12px 14px', background: KX2.glassInput, border: `1px solid ${KX2.glassLine}`, borderRadius: 12, outline: 'none', color: KX2.ink, fontSize: 15, fontFamily: KX2.fontUI, }} />
); } // ============ STEP 4 · Kayser folder created (the moat) ============ function StepDriveFolder({ onNext, data }) { const isGoogle = data.google; const [showOpen, setShowOpen] = React.useState(true); return (
📁
{isGoogle ? "I made a Kayser folder in your Drive." : "Here's your Kayser workspace."}
{isGoogle ? "Everything I write lives here, in your account, in formats you own. You can open any of it in Google Drive directly." : "Everything I save lives in your private Kayser workspace. You can connect Google later to keep it all in your own Drive."}
{/* Folder preview */}
📁 Drive / Kayser /
{[ { i: '📊', n: 'Customers.sheet', d: 'Auto-maintained · who they are, what they need' }, { i: '💰', n: 'Money.sheet', d: 'Auto-maintained · invoices, bills, P&L' }, { i: '✓', n: 'Tasks.sheet', d: 'Auto-maintained · to-do list synced to chat' }, { i: '📄', n: 'Notes / (folder)', d: 'One doc per customer · gate codes, quirks, history' }, { i: '📎', n: 'Files / (folder)', d: 'Receipts, photos, contracts · auto-filed' }, ].map(r => (
{r.i}
{r.n}
{r.d}
))}
You own your data. If you ever leave Kayser, your customer list, your books, your notes — they all stay in your Drive.
); } // ============ STEP 5 · First find (honest about readiness) ============ function StepFirstFind({ data, onComplete }) { const [phase, setPhase] = React.useState('settling'); // settling → ready React.useEffect(() => { const t = setTimeout(() => setPhase('ready'), 2200); return () => clearTimeout(t); }, []); const name = data.name || 'there'; const ind = KXV2_INDUSTRIES.find(i => i.id === data.industry); const industryLabel = data.industry === 'other' && data.industryOther ? data.industryOther : (ind?.label?.toLowerCase() || 'your business'); return (
K {phase === 'settling' && ( )}
{phase === 'settling' ? ( <>
Settling in…
Setting up your folder and learning the basics of {industryLabel}.
) : ( <>
Ready, {name}.
I'm going to spend the next few minutes reading through what I have access to. As I find things worth your attention, I'll surface them in chat.
WHAT TO EXPECT FIRST
{[ { i: '◌', t: 'A few "watching" notes', s: "Things I'm keeping an eye on, like unsent invoices or stale leads." }, { i: '◇', t: 'A couple of approvals', s: "Drafts I'd like to send — you tap Send, Edit, or Skip." }, { i: '✓', t: 'A trust dial that grows', s: "The more you approve, the more I'll handle on my own." }, ].map(x => (
{x.i}
{x.t}
{x.s}
))}
)}
); } // ============ Helpers ============ function primaryBtn() { return { width: '100%', padding: '14px', borderRadius: 14, border: 'none', background: KX2.brand, color: '#fff', fontFamily: KX2.fontUI, fontSize: 15, fontWeight: 600, cursor: 'pointer', boxShadow: '0 8px 24px rgba(124,58,237,.45)', }; } Object.assign(window, { KxV2Onboarding, KXV2_INDUSTRIES });