345 lines
17 KiB
JavaScript
345 lines
17 KiB
JavaScript
// ============================================================
|
||
// crm-onboarding.jsx — Cadence CRM · auth + onboarding
|
||
// ------------------------------------------------------------
|
||
// Full-screen flows that precede the app. Same minimal/light
|
||
// aesthetic as the Sidebar app shell. Built on vibn-ai-templates
|
||
// components (Button, Field, Input, Card, Badge, Avatar, Icon…).
|
||
// ============================================================
|
||
|
||
// Cadence brand mark — concentric "pulse" rings (a CRM cadence)
|
||
const CadenceMark = ({ size = 22 }) => (
|
||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<rect x="1" y="1" width="22" height="22" rx="7" fill="#5e5cff"/>
|
||
<path d="M5 13.5 H8.5 L10.2 8 L13 16 L14.8 11 L16.4 13.5 H19"
|
||
stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||
</svg>
|
||
);
|
||
|
||
const onbFont = "'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif";
|
||
|
||
// ── Shared scaffold: brand top bar + footer, centered slot ───
|
||
const OnbScaffold = ({ children, right, maxWidth = 460 }) => (
|
||
<div className="vibn-app" style={{
|
||
width: "100%", height: "100%", display: "grid",
|
||
gridTemplateRows: "auto 1fr auto", fontFamily: onbFont,
|
||
background: "var(--bg)", overflow: "hidden",
|
||
}}>
|
||
<header style={{
|
||
display: "flex", justifyContent: "space-between", alignItems: "center",
|
||
padding: "20px 28px",
|
||
}}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 9, fontWeight: 600, fontSize: 15 }}>
|
||
<CadenceMark size={22}/> Cadence
|
||
</div>
|
||
<div style={{ fontSize: 13, color: "var(--text-2)" }}>{right}</div>
|
||
</header>
|
||
<main style={{ display: "flex", alignItems: "center", justifyContent: "center", padding: 24, overflowY: "auto" }}>
|
||
<div style={{ width: maxWidth, maxWidth: "100%" }}>{children}</div>
|
||
</main>
|
||
<footer style={{
|
||
display: "flex", justifyContent: "space-between", alignItems: "center",
|
||
padding: "16px 28px", fontSize: 11, color: "var(--text-3)",
|
||
}}>
|
||
<span>© 2026 Cadence CRM</span>
|
||
<div style={{ display: "flex", gap: 16 }}><span>Privacy</span><span>Terms</span><span>Security</span></div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
|
||
const SocialButtons = ({ stacked }) => (
|
||
<div style={{ display: stacked ? "flex" : "flex", flexDirection: stacked ? "column" : "row", gap: 8 }}>
|
||
<Button variant="secondary" full>Continue with Google</Button>
|
||
<Button variant="secondary" full>Continue with Microsoft</Button>
|
||
</div>
|
||
);
|
||
|
||
// ── 1 · SIGN UP ──────────────────────────────────────────────
|
||
const CRMSignUp = () => (
|
||
<OnbScaffold right={<>Have an account? <b style={{ color: "var(--text)" }}>Sign in</b></>}>
|
||
<Card variant="raised" padding={34}>
|
||
<h1 style={{ margin: 0, fontSize: 24, fontWeight: 600, letterSpacing: "-0.02em" }}>
|
||
Create your Cadence account
|
||
</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 24px" }}>
|
||
Free for your whole team for 14 days. No card required.
|
||
</p>
|
||
<SocialButtons/>
|
||
<Divider label="or"/>
|
||
<Field label="Work email"><Input value="mira@northwind.io" autofocus/></Field>
|
||
<Field label="Full name"><Input placeholder="Mira Reyes"/></Field>
|
||
<Field label="Password" hint="At least 10 characters with a number.">
|
||
<Input type="password" value="••••••••••" trailingIcon={<Icon name="eye" size={14}/>}/>
|
||
</Field>
|
||
<Checkbox checked label="I agree to the Terms and Privacy Policy." style={{ margin: "2px 0 18px" }}/>
|
||
<Button full size="lg">Create account <Icon name="arrow" size={14}/></Button>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
|
||
// ── 2 · SIGN IN ──────────────────────────────────────────────
|
||
const CRMSignIn = () => (
|
||
<OnbScaffold right={<>New here? <b style={{ color: "var(--text)" }}>Create an account</b></>}>
|
||
<Card variant="raised" padding={34}>
|
||
<h1 style={{ margin: 0, fontSize: 24, fontWeight: 600, letterSpacing: "-0.02em" }}>Welcome back</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 24px" }}>
|
||
Sign in to the Northwind workspace.
|
||
</p>
|
||
<SocialButtons/>
|
||
<Divider label="or"/>
|
||
<Field label="Work email"><Input value="mira@northwind.io" autofocus/></Field>
|
||
<div style={{ marginBottom: 16 }}>
|
||
<div style={{
|
||
display: "flex", justifyContent: "space-between", alignItems: "baseline",
|
||
fontSize: 12, fontWeight: 500, marginBottom: 6,
|
||
}}>
|
||
<span>Password</span>
|
||
<span style={{ color: "var(--accent)", cursor: "pointer", fontWeight: 400 }}>Forgot?</span>
|
||
</div>
|
||
<Input type="password" value="••••••••••" trailingIcon={<Icon name="eye" size={14}/>}/>
|
||
</div>
|
||
<Checkbox checked label="Keep me signed in for 30 days" style={{ marginBottom: 18 }}/>
|
||
<Button full size="lg">Sign in <Icon name="arrow" size={14}/></Button>
|
||
<div style={{
|
||
marginTop: 18, padding: "10px 14px", borderRadius: "var(--radius)",
|
||
background: "var(--surface-2)", border: "1px solid var(--border)",
|
||
fontSize: 12, color: "var(--text-2)", display: "flex", alignItems: "center", gap: 10,
|
||
}}>
|
||
<Icon name="shield" size={14} style={{ color: "var(--accent)" }}/>
|
||
<span style={{ flex: 1 }}>Your company uses SSO?</span>
|
||
<span style={{ color: "var(--text)", fontWeight: 500, cursor: "pointer" }}>Use SSO →</span>
|
||
</div>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
|
||
// ── Stepper used across onboarding steps ─────────────────────
|
||
const Stepper = ({ step }) => {
|
||
const steps = ["Workspace", "About you", "Import", "Invite"];
|
||
return (
|
||
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 24 }}>
|
||
{steps.map((s, i) => {
|
||
const state = i < step ? "done" : i === step ? "active" : "todo";
|
||
return (
|
||
<React.Fragment key={s}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<div style={{
|
||
width: 22, height: 22, borderRadius: "50%",
|
||
background: state === "done" ? "var(--success)" : state === "active" ? "var(--text)" : "transparent",
|
||
color: state === "todo" ? "var(--text-3)" : "#fff",
|
||
border: state === "todo" ? "1px solid var(--border-strong)" : "none",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
fontSize: 11, fontWeight: 600, flexShrink: 0,
|
||
}}>{state === "done" ? <Icon name="checkOnly" size={12} stroke={2.6}/> : i + 1}</div>
|
||
<span style={{
|
||
fontSize: 12, color: state === "todo" ? "var(--text-3)" : "var(--text)",
|
||
fontWeight: state === "active" ? 600 : 400, whiteSpace: "nowrap",
|
||
}}>{s}</span>
|
||
</div>
|
||
{i < steps.length - 1 && <div style={{ flex: 1, height: 1, background: "var(--border)", margin: "0 4px", minWidth: 16 }}/>}
|
||
</React.Fragment>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const Tile = ({ icon, title, sub, selected }) => (
|
||
<div style={{
|
||
padding: 16, borderRadius: "var(--radius)", cursor: "pointer", textAlign: "left",
|
||
border: selected ? "1.5px solid var(--accent)" : "1px solid var(--border)",
|
||
background: selected ? "var(--accent-soft)" : "var(--surface)",
|
||
boxShadow: selected ? "0 0 0 3px var(--accent-ring)" : "var(--shadow-sm)",
|
||
position: "relative",
|
||
}}>
|
||
<div style={{
|
||
width: 32, height: 32, borderRadius: 9, marginBottom: 12,
|
||
background: selected ? "var(--accent)" : "var(--surface-alt)",
|
||
color: selected ? "#fff" : "var(--text-2)",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
}}><Icon name={icon} size={16}/></div>
|
||
<div style={{ fontSize: 13, fontWeight: 600 }}>{title}</div>
|
||
<div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 2, lineHeight: 1.4 }}>{sub}</div>
|
||
{selected && <div style={{
|
||
position: "absolute", top: 14, right: 14, width: 18, height: 18, borderRadius: "50%",
|
||
background: "var(--accent)", color: "#fff",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
}}><Icon name="checkOnly" size={11} stroke={2.6}/></div>}
|
||
</div>
|
||
);
|
||
|
||
// ── 3 · ONBOARDING · step 1 — create workspace ───────────────
|
||
const CRMOnbWorkspace = () => (
|
||
<OnbScaffold right="Step 1 of 4" maxWidth={560}>
|
||
<Card variant="raised" padding={36}>
|
||
<Stepper step={0}/>
|
||
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 600, letterSpacing: "-0.02em" }}>
|
||
Name your workspace
|
||
</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 24px" }}>
|
||
This is where your team's contacts, companies and deals will live.
|
||
</p>
|
||
|
||
<div style={{ display: "flex", gap: 16, alignItems: "flex-start", marginBottom: 18 }}>
|
||
<div style={{
|
||
width: 64, height: 64, borderRadius: 14, flexShrink: 0,
|
||
background: "var(--surface-alt)", border: "1.5px dashed var(--border-strong)",
|
||
display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
|
||
color: "var(--text-3)", cursor: "pointer", gap: 2,
|
||
}}>
|
||
<Icon name="plus" size={18}/>
|
||
<span style={{ fontSize: 9 }}>Logo</span>
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<Field label="Workspace name" style={{ marginBottom: 12 }}>
|
||
<Input value="Northwind"/>
|
||
</Field>
|
||
<Field label="Workspace URL" hint="You can change this later." style={{ marginBottom: 0 }}>
|
||
<Input value="northwind" leadingIcon={<span style={{ fontSize: 12, color: "var(--text-3)" }}>cadence.app/</span>}/>
|
||
</Field>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 26 }}>
|
||
<span style={{ fontSize: 12, color: "var(--text-3)" }}>You can invite teammates in a moment.</span>
|
||
<Button size="lg">Continue <Icon name="arrow" size={14}/></Button>
|
||
</div>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
|
||
// ── 4 · ONBOARDING · step 2 — about you ──────────────────────
|
||
const CRMOnbAbout = () => (
|
||
<OnbScaffold right="Step 2 of 4" maxWidth={620}>
|
||
<Card variant="raised" padding={36}>
|
||
<Stepper step={1}/>
|
||
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 600, letterSpacing: "-0.02em" }}>
|
||
Tell us about your team
|
||
</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 22px" }}>
|
||
We'll tailor your pipeline stages and views to match.
|
||
</p>
|
||
|
||
<div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10 }}>What will you use Cadence for?</div>
|
||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 24 }}>
|
||
<Tile icon="target" title="Sales pipeline" sub="Track deals to close" selected/>
|
||
<Tile icon="people" title="Relationships" sub="Nurture contacts"/>
|
||
<Tile icon="briefcase" title="Recruiting" sub="Manage candidates"/>
|
||
<Tile icon="bolt" title="Fundraising" sub="Investors & rounds"/>
|
||
<Tile icon="inbox" title="Support" sub="Customer success"/>
|
||
<Tile icon="spark" title="Something else" sub="I'll set it up"/>
|
||
</div>
|
||
|
||
<div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10 }}>How big is your team?</div>
|
||
<FieldGroup options={["Just me", "2–10", "11–50", "51–200", "200+"]} value="2–10"/>
|
||
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 28 }}>
|
||
<Button variant="ghost">← Back</Button>
|
||
<Button size="lg">Continue <Icon name="arrow" size={14}/></Button>
|
||
</div>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
|
||
// ── 5 · ONBOARDING · step 3 — import contacts ────────────────
|
||
const CRMOnbImport = () => {
|
||
const Source = ({ icon, title, sub, badge }) => (
|
||
<div style={{
|
||
display: "flex", alignItems: "center", gap: 14, padding: 16,
|
||
borderRadius: "var(--radius)", border: "1px solid var(--border)",
|
||
background: "var(--surface)", cursor: "pointer", boxShadow: "var(--shadow-sm)",
|
||
}}>
|
||
<div style={{
|
||
width: 40, height: 40, borderRadius: 10, flexShrink: 0,
|
||
background: "var(--surface-alt)", color: "var(--text-2)",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
}}><Icon name={icon} size={18}/></div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ fontSize: 14, fontWeight: 600, display: "flex", alignItems: "center", gap: 8 }}>
|
||
{title}{badge && <Badge tone="accent">{badge}</Badge>}
|
||
</div>
|
||
<div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 2 }}>{sub}</div>
|
||
</div>
|
||
<Icon name="chevRight" size={16} style={{ color: "var(--text-3)" }}/>
|
||
</div>
|
||
);
|
||
return (
|
||
<OnbScaffold right="Step 3 of 4" maxWidth={560}>
|
||
<Card variant="raised" padding={36}>
|
||
<Stepper step={2}/>
|
||
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 600, letterSpacing: "-0.02em" }}>
|
||
Bring your contacts in
|
||
</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 22px" }}>
|
||
Cadence dedupes and enriches automatically. Nothing is shared.
|
||
</p>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||
<Source icon="people" title="Google Contacts" sub="Sync 2,400 contacts" badge="Recommended"/>
|
||
<Source icon="inbox" title="Connect a mailbox" sub="Build contacts from your sent mail"/>
|
||
<Source icon="doc" title="Upload a CSV" sub="Map columns to fields"/>
|
||
</div>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 26 }}>
|
||
<Button variant="ghost">Skip for now</Button>
|
||
<Button size="lg">Continue <Icon name="arrow" size={14}/></Button>
|
||
</div>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
};
|
||
|
||
// ── 6 · ONBOARDING · step 4 — invite team ────────────────────
|
||
const CRMOnbInvite = () => {
|
||
const Row = ({ email, role, color }) => (
|
||
<div style={{
|
||
display: "flex", alignItems: "center", gap: 12, padding: "10px 12px",
|
||
borderRadius: "var(--radius)", background: "var(--surface-2)", border: "1px solid var(--border)",
|
||
}}>
|
||
<Avatar name={email} color={color} size={28}/>
|
||
<span style={{ flex: 1, fontSize: 13, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{email}</span>
|
||
<Select value={role} style={{ padding: "5px 10px" }}/>
|
||
<Badge tone="warn" dot>Pending</Badge>
|
||
</div>
|
||
);
|
||
return (
|
||
<OnbScaffold right="Step 4 of 4" maxWidth={560}>
|
||
<Card variant="raised" padding={36}>
|
||
<Stepper step={3}/>
|
||
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 600, letterSpacing: "-0.02em" }}>
|
||
Invite your team
|
||
</h1>
|
||
<p style={{ fontSize: 13, color: "var(--text-2)", margin: "8px 0 22px" }}>
|
||
Cadence is better with the people you sell with.
|
||
</p>
|
||
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
||
<Input placeholder="name@northwind.io, separate with commas" style={{ flex: 1 }} autofocus/>
|
||
<Button>Send invites</Button>
|
||
</div>
|
||
<div style={{ fontSize: 11, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 500, marginBottom: 8 }}>
|
||
To be invited · 3
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||
<Row email="theo@northwind.io" role="Admin" color="#c8e8a8"/>
|
||
<Row email="devi@northwind.io" role="Member" color="#a8c8e8"/>
|
||
<Row email="sun@northwind.io" role="Member" color="#e8a87c"/>
|
||
</div>
|
||
<div style={{
|
||
marginTop: 18, padding: "12px 14px", borderRadius: "var(--radius)",
|
||
border: "1px dashed var(--border-strong)", display: "flex", alignItems: "center", gap: 12,
|
||
}}>
|
||
<Icon name="link" size={16} style={{ color: "var(--accent)" }}/>
|
||
<span style={{ flex: 1, fontSize: 13, fontFamily: "var(--font-mono)", color: "var(--text-2)" }}>cadence.app/join/northwind-7f4a…</span>
|
||
<Button variant="secondary" size="sm">Copy link</Button>
|
||
</div>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 26 }}>
|
||
<Button variant="ghost">I'll do this later</Button>
|
||
<Button size="lg">Enter Cadence <Icon name="arrow" size={14}/></Button>
|
||
</div>
|
||
</Card>
|
||
</OnbScaffold>
|
||
);
|
||
};
|
||
|
||
Object.assign(window, {
|
||
CadenceMark, CRMSignUp, CRMSignIn,
|
||
CRMOnbWorkspace, CRMOnbAbout, CRMOnbImport, CRMOnbInvite,
|
||
});
|