// 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 && (
Back
)}
{step > 0 && step < totalSteps - 1 && (
Skip setup
)}
{/* 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) => (
))}
Let's go →
);
}
// ============ 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 */}
Continue with Google
{/* 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 => (
))}
{/* 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 */}
setManualOpen(o => !o)} style={{
background: 'transparent', border: 'none', color: KX2.ink3,
cursor: 'pointer', fontFamily: KX2.fontUI, fontSize: 13,
textDecoration: 'underline', textUnderlineOffset: 3,
}}>
Don't use Google?
{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, imap: p }))} style={{
padding: '8px', borderRadius: 8, fontSize: 12, fontFamily: KX2.fontUI,
background: manualData.imap === p ? KX2.brandSoft : KX2.glassInput,
border: `1px solid ${manualData.imap === p ? KX2.brand : KX2.glassLine}`,
color: KX2.ink, cursor: 'pointer', textTransform: 'capitalize',
}}>{p}
))}
setManualData(d => ({ ...d, calendarUrl: v }))} />
Continue with email-only
)}
);
}
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 => (
set('industry', i.id)} style={{
padding: '12px 12px', borderRadius: 12, textAlign: 'left',
background: data.industry === i.id ? KX2.brandSoft : KX2.glassInput,
border: `1px solid ${data.industry === i.id ? KX2.brand : KX2.glassLine}`,
color: KX2.ink, fontFamily: KX2.fontUI, cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 10,
}}>
{i.emoji}
{i.label}
))}
{isOther && (
set('industryOther', v)} />
I'll ask a few quick questions in chat once we're done so I get your vocabulary right.
)}
Continue →
);
}
// ============ 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.
Continue →
);
}
function OnbPicker({ label, options, value, onChange }) {
return (
{label}
{options.map(o => (
onChange(o.id)} style={{
padding: '11px 14px', borderRadius: 10, textAlign: 'left',
background: value === o.id ? KX2.brandSoft : KX2.glassInput,
border: `1px solid ${value === o.id ? KX2.brand : KX2.glassLine}`,
color: KX2.ink, fontFamily: KX2.fontUI, cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 10,
}}>
{o.label}
{o.recommended && (
BEST
)}
{o.sub &&
{o.sub}
}
{value === o.id && ✓ }
))}
);
}
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 => (
))}
You own your data. If you ever leave Kayser, your customer list, your books, your notes — they all stay in your Drive.
Looks good →
);
}
// ============ 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 => (
))}
onComplete?.(data)} style={primaryBtn()}>
Take me in →
>
)}
);
}
// ============ 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 });