feat: flatten routes and merge marketing and onboarding directories
This commit is contained in:
72
design-templates/VIBN (2)/vibn-crm/README.md
Normal file
72
design-templates/VIBN (2)/vibn-crm/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Cadence CRM — Sidebar template package
|
||||
|
||||
A complete CRM screen set in the **far-left Sidebar** navigation style, built on the **vibn-ai-templates** component library in the light/minimal theme.
|
||||
|
||||
Worked example brand: **Cadence** (the product) / **Northwind** (the example workspace).
|
||||
|
||||
## Screens (13)
|
||||
|
||||
**Auth + onboarding** (full-screen, no sidebar yet — standard pattern)
|
||||
| Screen | File |
|
||||
|---|---|
|
||||
| Sign up | `crm-onboarding.jsx` → `CRMSignUp` |
|
||||
| Sign in | `crm-onboarding.jsx` → `CRMSignIn` |
|
||||
| Onboarding 1 · Name workspace | `CRMOnbWorkspace` |
|
||||
| Onboarding 2 · About your team | `CRMOnbAbout` |
|
||||
| Onboarding 3 · Import contacts | `CRMOnbImport` |
|
||||
| Onboarding 4 · Invite team | `CRMOnbInvite` |
|
||||
|
||||
**In-app** (every screen rendered inside `SidebarShell`)
|
||||
| Screen | File |
|
||||
|---|---|
|
||||
| Home / dashboard | `crm-pages.jsx` → `CRMHome` |
|
||||
| People (contacts table) | `CRMPeople` |
|
||||
| Company record (detail) | `CRMRecord` |
|
||||
| Deals (pipeline kanban) | `CRMPipeline` |
|
||||
| Inbox (threads + conversation) | `CRMInbox` |
|
||||
| Reports (charts + leaderboard) | `CRMReports` |
|
||||
| Settings → Members (admin) | `CRMSettings` |
|
||||
|
||||
## The sidebar pattern
|
||||
|
||||
Every in-app screen wraps its body in `CRMShell`, which configures the kit's `SidebarShell`:
|
||||
|
||||
```jsx
|
||||
const CRMShell = ({ active, children }) => (
|
||||
<SidebarShell
|
||||
brand={{ name: "Northwind", mark: <CadenceMark/> }}
|
||||
sections={crmSections(active)} // sets the active nav item
|
||||
user={{ name: "Mira Reyes", email: "mira@northwind.io" }}
|
||||
search="Search or jump to…"
|
||||
>
|
||||
{children}
|
||||
</SidebarShell>
|
||||
);
|
||||
```
|
||||
|
||||
The nav is defined once in `crmSections(active)` — three groups: top-level (Home / Inbox / Tasks), **Records** (Companies / People / Deals), **Workspace** (Reports / Automations / Settings). Pass the active id and the matching item highlights.
|
||||
|
||||
## Dependencies & load order
|
||||
|
||||
This package depends on `vibn-ai-templates`. Load order matters:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="vibn-ai-templates/tokens.css">
|
||||
|
||||
<script type="text/babel" src="vibn-ai-templates/icons.jsx"></script>
|
||||
<script type="text/babel" src="vibn-ai-templates/components.jsx"></script>
|
||||
<script type="text/babel" src="vibn-ai-templates/shells.jsx"></script>
|
||||
<script type="text/babel" src="vibn-crm/crm-onboarding.jsx"></script>
|
||||
<script type="text/babel" src="vibn-crm/crm-pages.jsx"></script>
|
||||
```
|
||||
|
||||
Wrap the app in `class="theme-minimal"` (the default light theme). Because the whole kit is token-driven, you can re-skin the entire CRM to dark by swapping that one class to `theme-dark` — no page edits.
|
||||
|
||||
## Showcase
|
||||
|
||||
`Cadence CRM Templates.html` lays all 13 screens out on a design canvas in three sections (auth, onboarding, in-app). Drag artboards to reorder, or open any one fullscreen (←/→/Esc).
|
||||
|
||||
## What's template vs. reusable
|
||||
|
||||
- **Reusable** — everything in `vibn-ai-templates` (Button, Table, Card, Badge, Avatar, Tabs, Input, SidebarShell, …).
|
||||
- **Template** — `crm-pages.jsx` and `crm-onboarding.jsx` are *compositions*. They show how to assemble the kit into real CRM screens. Copy them as starting points and swap in your data.
|
||||
344
design-templates/VIBN (2)/vibn-crm/crm-onboarding.jsx
Normal file
344
design-templates/VIBN (2)/vibn-crm/crm-onboarding.jsx
Normal file
@@ -0,0 +1,344 @@
|
||||
// ============================================================
|
||||
// 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,
|
||||
});
|
||||
688
design-templates/VIBN (2)/vibn-crm/crm-pages.jsx
Normal file
688
design-templates/VIBN (2)/vibn-crm/crm-pages.jsx
Normal file
@@ -0,0 +1,688 @@
|
||||
// ============================================================
|
||||
// crm-pages.jsx — Cadence CRM · in-app screens
|
||||
// ------------------------------------------------------------
|
||||
// Every page renders INSIDE SidebarShell (far-left sidebar).
|
||||
// Built on vibn-ai-templates components. Light/minimal theme.
|
||||
//
|
||||
// Pages: CRMHome, CRMPeople, CRMRecord, CRMPipeline,
|
||||
// CRMInbox, CRMReports, CRMSettings.
|
||||
// ============================================================
|
||||
|
||||
const CRM_USER = { name: "Mira Reyes", email: "mira@northwind.io", color: "#d4b8a8" };
|
||||
|
||||
// Sidebar config — pass the active id, get the sections array.
|
||||
const crmSections = (active) => [
|
||||
{ items: [
|
||||
{ id: "home", label: "Home", icon: "home", active: active === "home" },
|
||||
{ id: "inbox", label: "Inbox", icon: "inbox", count: 8, active: active === "inbox" },
|
||||
{ id: "tasks", label: "My tasks", icon: "check", count: 3, active: active === "tasks" },
|
||||
]},
|
||||
{ title: "Records", items: [
|
||||
{ id: "companies", label: "Companies", icon: "building", active: active === "companies" },
|
||||
{ id: "people", label: "People", icon: "people", active: active === "people" },
|
||||
{ id: "deals", label: "Deals", icon: "target", count: 12, active: active === "deals" },
|
||||
]},
|
||||
{ title: "Workspace", items: [
|
||||
{ id: "reports", label: "Reports", icon: "bar", active: active === "reports" },
|
||||
{ id: "automations", label: "Automations", icon: "workflow", active: active === "automations" },
|
||||
{ id: "settings", label: "Settings", icon: "settings", active: active === "settings" },
|
||||
]},
|
||||
];
|
||||
|
||||
// Wrap a page body in the shell with the right nav item active
|
||||
const CRMShell = ({ active, children }) => (
|
||||
<SidebarShell brand={{ name: "Northwind", mark: <CadenceMark size={22}/> }}
|
||||
sections={crmSections(active)} user={CRM_USER} search="Search or jump to…">
|
||||
{children}
|
||||
</SidebarShell>
|
||||
);
|
||||
|
||||
// Reusable page header bar (title + actions row)
|
||||
const PageBar = ({ title, sub, breadcrumb, actions }) => (
|
||||
<div style={{
|
||||
padding: "16px 28px", borderBottom: "1px solid var(--divider)",
|
||||
background: "var(--surface)", display: "flex",
|
||||
justifyContent: "space-between", alignItems: "center", gap: 16,
|
||||
}}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
{breadcrumb && <div style={{ fontSize: 12, color: "var(--text-3)", marginBottom: 3 }}>{breadcrumb}</div>}
|
||||
<h1 style={{ margin: 0, fontSize: 20, fontWeight: 600, letterSpacing: "-0.01em" }}>{title}</h1>
|
||||
{sub && <div style={{ fontSize: 13, color: "var(--text-2)", marginTop: 2 }}>{sub}</div>}
|
||||
</div>
|
||||
{actions && <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>{actions}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Scroll = ({ children, pad = 28 }) => (
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: pad }}>{children}</div>
|
||||
);
|
||||
|
||||
// ── small stat tile ──────────────────────────────────────────
|
||||
const Stat = ({ label, value, delta, up, spark }) => (
|
||||
<Card padding={18}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
|
||||
<span style={{ fontSize: 12, color: "var(--text-2)" }}>{label}</span>
|
||||
{delta && <span style={{ fontSize: 11, fontWeight: 600, color: up ? "var(--success)" : "var(--danger)" }}>
|
||||
{up ? "↑" : "↓"} {delta}</span>}
|
||||
</div>
|
||||
<div style={{ fontSize: 28, fontWeight: 600, letterSpacing: "-0.02em", marginTop: 8 }}>{value}</div>
|
||||
{spark && (
|
||||
<svg viewBox="0 0 100 28" preserveAspectRatio="none" style={{ width: "100%", height: 24, marginTop: 6, display: "block" }}>
|
||||
<polyline points={spark} fill="none" stroke={up ? "var(--success)" : "var(--danger)"} strokeWidth="1.5" vectorEffect="non-scaling-stroke"/>
|
||||
</svg>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
const sparkUp = "0,24 12,20 24,22 36,15 48,17 60,10 72,12 84,6 100,2";
|
||||
const sparkDn = "0,6 14,8 28,7 42,12 56,11 70,16 84,15 100,20";
|
||||
|
||||
// ============================================================
|
||||
// 1 · HOME
|
||||
// ============================================================
|
||||
const CRMHome = () => (
|
||||
<CRMShell active="home">
|
||||
<PageBar title="Good morning, Mira"
|
||||
sub="3 deals need a nudge today · 8 unread in Inbox · 1 task overdue"
|
||||
actions={<>
|
||||
<Button variant="secondary" leadingIcon={<Icon name="bell" size={13}/>}>Notifications</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>New deal</Button>
|
||||
</>}/>
|
||||
<Scroll>
|
||||
{/* KPI row */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 14, marginBottom: 20 }}>
|
||||
<Stat label="Open pipeline" value="$1.24M" delta="12%" up spark={sparkUp}/>
|
||||
<Stat label="Won · this month" value="$284K" delta="8%" up spark={sparkUp}/>
|
||||
<Stat label="Win rate · 90d" value="31%" delta="2pt" up spark={sparkUp}/>
|
||||
<Stat label="Avg. response" value="2.4h" delta="0.6h" up={false} spark={sparkDn}/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 20 }}>
|
||||
{/* Pipeline by stage */}
|
||||
<Card padding={0}>
|
||||
<CardHeader title="Pipeline by stage" subtitle="Q2 · 12 active deals"
|
||||
style={{ padding: "16px 20px", margin: 0, borderBottom: "1px solid var(--divider)" }}
|
||||
action={<Button variant="ghost" size="sm">View board →</Button>}/>
|
||||
<div style={{ padding: "12px 20px 18px" }}>
|
||||
{[
|
||||
["Lead", 5, "$420K", 100],
|
||||
["Qualified", 3, "$365K", 78],
|
||||
["Proposal", 2, "$280K", 60],
|
||||
["Negotiation", 1, "$148K", 32],
|
||||
["Won", 1, "$96K", 22],
|
||||
].map(([name, n, val, w]) => (
|
||||
<div key={name} style={{ padding: "8px 0" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 6, fontSize: 13 }}>
|
||||
<span>{name}</span>
|
||||
<span style={{ color: "var(--text-2)" }}>{n} deals · <b style={{ color: "var(--text)" }}>{val}</b></span>
|
||||
</div>
|
||||
<div style={{ height: 8, borderRadius: 4, background: "var(--surface-alt)", overflow: "hidden" }}>
|
||||
<div style={{ width: `${w}%`, height: "100%", background: "linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 55%, transparent))", borderRadius: 4 }}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Today's tasks */}
|
||||
<Card padding={0}>
|
||||
<CardHeader title="Today" subtitle="3 tasks"
|
||||
style={{ padding: "16px 20px", margin: 0, borderBottom: "1px solid var(--divider)" }}
|
||||
action={<Button variant="ghost" size="sm">All tasks</Button>}/>
|
||||
<div style={{ padding: "6px 12px 12px" }}>
|
||||
{[
|
||||
["Follow up with Acme Robotics", "Overdue · 2d", true, "danger"],
|
||||
["Send proposal to Halcyon", "Due 2:00pm", false, "warn"],
|
||||
["Call Sun at Northstar", "Due 4:30pm", false, "neutral"],
|
||||
["Prep QBR deck for Kestrel", "Tomorrow", false, "neutral"],
|
||||
].map(([t, when, overdue, tone], i) => (
|
||||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 8px", borderBottom: i < 3 ? "1px solid var(--divider)" : "none" }}>
|
||||
<Checkbox checked={false}/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500 }}>{t}</div>
|
||||
<div style={{ fontSize: 11, color: overdue ? "var(--danger)" : "var(--text-3)", marginTop: 1 }}>{when}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent activity */}
|
||||
<Card padding={0} style={{ marginTop: 20 }}>
|
||||
<CardHeader title="Recent activity"
|
||||
style={{ padding: "16px 20px", margin: 0, borderBottom: "1px solid var(--divider)" }}
|
||||
action={<Tabs variant="pill" items={[{ label: "All" }, { label: "Deals" }, { label: "Emails" }]} active="All"/>}/>
|
||||
<div style={{ padding: "8px 20px 16px" }}>
|
||||
{[
|
||||
["MR", "#d4b8a8", "Mira Reyes", "moved", "Acme — Renewal '26 to Negotiation", "12m"],
|
||||
["TR", "#c8e8a8", "Theo Roux", "logged a call with", "Sun Kim · Northstar", "1h"],
|
||||
["DP", "#a8c8e8", "Devi Patel", "won", "Halcyon · Pro renewal · $24K", "3h"],
|
||||
["SK", "#e8a87c", "Sun Ortiz", "added 4 contacts to", "Kestrel", "Yesterday"],
|
||||
].map(([i, c, who, verb, obj, t], idx) => (
|
||||
<div key={idx} style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 0", borderTop: idx ? "1px solid var(--divider)" : "none" }}>
|
||||
<Avatar name={who} color={c} size={28}/>
|
||||
<div style={{ flex: 1, fontSize: 13 }}>
|
||||
<b style={{ fontWeight: 600 }}>{who}</b>
|
||||
<span style={{ color: "var(--text-2)" }}> {verb} </span>
|
||||
<b style={{ fontWeight: 500 }}>{obj}</b>
|
||||
</div>
|
||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>{t}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</Scroll>
|
||||
</CRMShell>
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// 2 · PEOPLE (contacts table)
|
||||
// ============================================================
|
||||
const CRMPeople = () => {
|
||||
const people = [
|
||||
{ id: 1, name: "Iris Tanaka", color: "#e8a87c", title: "Head of Engineering", company: "Acme Robotics", stage: "Customer", owner: "MR", last: "2h" },
|
||||
{ id: 2, name: "Daniel Owusu", color: "#a8c8e8", title: "VP Product", company: "Acme Robotics", stage: "Customer", owner: "MR", last: "Yesterday" },
|
||||
{ id: 3, name: "Sun Kim", color: "#c8e8a8", title: "VP Operations", company: "Northstar", stage: "Lead", owner: "TR", last: "3d" },
|
||||
{ id: 4, name: "Priya Nair", color: "#c8a8e8", title: "COO", company: "Halcyon", stage: "Prospect", owner: "DP", last: "1w" },
|
||||
{ id: 5, name: "Marco Lindqvist", color: "#e8c8a8", title: "Procurement", company: "Kestrel", stage: "Customer", owner: "MR", last: "4d" },
|
||||
{ id: 6, name: "Naila Choudhury", color: "#a8e8c8", title: "CFO", company: "Mossbank", stage: "Lead", owner: "TR", last: "2w" },
|
||||
{ id: 7, name: "Henri Lamarck", color: "#e8a8c8", title: "Founder", company: "Verra", stage: "Prospect", owner: "DP", last: "5d" },
|
||||
{ id: 8, name: "Emi Hara", color: "#d4b8a8", title: "Head of Sales", company: "Tide Co.", stage: "Customer", owner: "MR", last: "1d" },
|
||||
];
|
||||
const stageTone = { Customer: "success", Lead: "accent", Prospect: "warn" };
|
||||
return (
|
||||
<CRMShell active="people">
|
||||
<PageBar title="People" sub="2,418 contacts"
|
||||
actions={<>
|
||||
<Button variant="secondary" leadingIcon={<Icon name="doc" size={13}/>}>Import</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>New contact</Button>
|
||||
</>}/>
|
||||
{/* Filter row */}
|
||||
<div style={{ padding: "12px 28px", borderBottom: "1px solid var(--divider)", display: "flex", alignItems: "center", gap: 10, background: "var(--surface)" }}>
|
||||
<Input placeholder="Search people" leadingIcon={<Icon name="search" size={13}/>} style={{ width: 260, padding: "6px 10px" }}/>
|
||||
{["Stage", "Owner", "Company", "Last activity"].map(f => (
|
||||
<div key={f} style={{
|
||||
display: "flex", alignItems: "center", gap: 6, padding: "6px 10px",
|
||||
border: "1px dashed var(--border)", borderRadius: "var(--radius-sm)",
|
||||
fontSize: 12, color: "var(--text-2)", cursor: "pointer",
|
||||
}}>{f}<Icon name="chevDown" size={11}/></div>
|
||||
))}
|
||||
<div style={{ flex: 1 }}/>
|
||||
<FieldGroup options={["Table", "Board"]} value="Table"/>
|
||||
</div>
|
||||
<Scroll pad={20}>
|
||||
<Table
|
||||
selectable selected={[1, 5]}
|
||||
columns={[
|
||||
{ key: "name", label: "Name", render: r => (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<Avatar name={r.name} color={r.color} size={28}/>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{r.name}</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{r.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
)},
|
||||
{ key: "company", label: "Company", render: r => (
|
||||
<span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
|
||||
<span style={{ width: 20, height: 20, borderRadius: 5, background: "var(--surface-alt)", display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: "var(--text-2)" }}>{r.company[0]}</span>
|
||||
{r.company}
|
||||
</span>
|
||||
)},
|
||||
{ key: "stage", label: "Stage", render: r => <Badge tone={stageTone[r.stage]} dot>{r.stage}</Badge> },
|
||||
{ key: "owner", label: "Owner", render: r => <Avatar name={r.owner} size={24}/> },
|
||||
{ key: "last", label: "Last activity" },
|
||||
{ key: "act", label: "", align: "right", width: 40, render: () => <IconButton name="more" size="sm" label="More"/> },
|
||||
]}
|
||||
rows={people}
|
||||
/>
|
||||
</Scroll>
|
||||
</CRMShell>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 3 · COMPANY RECORD (detail)
|
||||
// ============================================================
|
||||
const CRMRecord = () => (
|
||||
<CRMShell active="companies">
|
||||
<PageBar breadcrumb="Companies › Acme Robotics" title="Acme Robotics"
|
||||
actions={<>
|
||||
<Button variant="secondary" leadingIcon={<Icon name="star" size={13}/>}>Follow</Button>
|
||||
<Button variant="secondary">Share</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>Log activity</Button>
|
||||
</>}/>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "320px 1fr", flex: 1, overflow: "hidden" }}>
|
||||
{/* Details rail */}
|
||||
<div style={{ borderRight: "1px solid var(--divider)", overflowY: "auto", padding: "20px 22px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 18 }}>
|
||||
<div style={{ width: 52, height: 52, borderRadius: 12, background: "linear-gradient(135deg, #ff8a3a, #f43f5e)", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 22, fontWeight: 700 }}>A</div>
|
||||
<div>
|
||||
<div style={{ display: "flex", gap: 6 }}>
|
||||
<Badge tone="success" dot>Customer</Badge>
|
||||
<Badge tone="accent">Tier 1</Badge>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 6 }}>acme-robotics.io</div>
|
||||
</div>
|
||||
</div>
|
||||
{[
|
||||
["Industry", "Industrial automation"],
|
||||
["Employees", "210"],
|
||||
["Owner", "Mira Reyes"],
|
||||
["Renewal", "Sept 1, 2026"],
|
||||
["ARR", "$148,000"],
|
||||
["Health", "Strong"],
|
||||
].map(([k, v]) => (
|
||||
<div key={k} style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: 10, padding: "8px 0", fontSize: 13, borderBottom: "1px solid var(--divider)" }}>
|
||||
<span style={{ color: "var(--text-3)" }}>{k}</span>
|
||||
<span style={{ fontWeight: 500 }}>{v}</span>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 500, margin: "18px 0 8px" }}>Open deals · 3</div>
|
||||
{[
|
||||
["Renewal '26", "$148K", "Negotiation", 70],
|
||||
["Vision platform", "$62K", "Discovery", 30],
|
||||
["Edge SDK pilot", "$24K", "Proposal", 45],
|
||||
].map(([n, v, s, p]) => (
|
||||
<div key={n} style={{ padding: "10px 12px", borderRadius: "var(--radius)", background: "var(--surface)", border: "1px solid var(--border)", marginBottom: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: 8, fontSize: 13, marginBottom: 6 }}>
|
||||
<span style={{ fontWeight: 500, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{n}</span>
|
||||
<span style={{ color: "var(--text-2)", flexShrink: 0 }}>{v}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, color: "var(--text-3)", marginBottom: 4 }}>
|
||||
<span>{s}</span><span>{p}%</span>
|
||||
</div>
|
||||
<div style={{ height: 3, borderRadius: 2, background: "var(--surface-alt)", overflow: "hidden" }}>
|
||||
<div style={{ width: `${p}%`, height: "100%", background: p > 60 ? "var(--success)" : "var(--accent)" }}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main: tabs + activity */}
|
||||
<div style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||
<div style={{ padding: "0 28px", borderBottom: "1px solid var(--divider)", background: "var(--surface)" }}>
|
||||
<Tabs items={[{ label: "Activity", count: 28 }, { label: "Notes", count: 7 }, { label: "People", count: 4 }, { label: "Files" }, { label: "Emails" }]} active="Activity"/>
|
||||
</div>
|
||||
<Scroll>
|
||||
{/* KPIs */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 22 }}>
|
||||
{[["Pipeline", "$234K", "+$12K 30d"], ["Lifetime", "$420K", "won"], ["Open deals", "3", "1 stalled"], ["Health", "82/100", "stable"]].map(([l, v, s]) => (
|
||||
<Card key={l} padding={14}>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{l}</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 600, marginTop: 4 }}>{v}</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-2)", marginTop: 2 }}>{s}</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{/* Composer */}
|
||||
<div style={{ display: "flex", gap: 10, padding: 12, borderRadius: "var(--radius)", border: "1px solid var(--border)", background: "var(--surface)", marginBottom: 20 }}>
|
||||
<Avatar name="Mira Reyes" color="#d4b8a8" size={28}/>
|
||||
<input placeholder="Log a note, call, or email…" style={{ flex: 1, border: "none", outline: "none", background: "transparent", fontSize: 13, fontFamily: "var(--font-sans)", color: "var(--text)" }}/>
|
||||
<Button size="sm">Log</Button>
|
||||
</div>
|
||||
{/* Timeline */}
|
||||
<div style={{ position: "relative", paddingLeft: 22 }}>
|
||||
<div style={{ position: "absolute", left: 9, top: 6, bottom: 6, width: 1, background: "var(--border)" }}/>
|
||||
{[
|
||||
["var(--success)", "Deal moved to Negotiation", "Mira · Renewal '26 · $148,000", "2h ago"],
|
||||
["var(--accent)", "Email sent · proposal v4", "To Iris, Daniel — opened 6 times", "Yesterday"],
|
||||
["var(--warn)", "Call logged · 32 min", "Theo — walkthrough with ops lead", "2d ago"],
|
||||
["var(--text-3)", "Note added", "They need SSO + SCIM by Sept — gating item", "4d ago"],
|
||||
].map(([dot, t, sub, when], i) => (
|
||||
<div key={i} style={{ position: "relative", marginBottom: 18 }}>
|
||||
<span style={{ position: "absolute", left: -19, top: 3, width: 11, height: 11, borderRadius: "50%", background: dot, boxShadow: "0 0 0 3px var(--bg)" }}/>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<span style={{ fontSize: 13, fontWeight: 500 }}>{t}</span>
|
||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>{when}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-2)", marginTop: 2 }}>{sub}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Scroll>
|
||||
</div>
|
||||
</div>
|
||||
</CRMShell>
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// 4 · PIPELINE (deals kanban)
|
||||
// ============================================================
|
||||
const CRMPipeline = () => {
|
||||
const columns = [
|
||||
{ name: "Lead", total: "$420K", tone: "var(--text-3)", deals: [
|
||||
{ co: "Mossbank", title: "New logo · Platform", val: "$120K", owner: "TR", color: "#a8e8c8", days: 3, tags: ["Inbound"] },
|
||||
{ co: "Verra", title: "Vision pilot", val: "$84K", owner: "DP", color: "#e8a8c8", days: 8 },
|
||||
{ co: "Tide Co.", title: "Expansion", val: "$96K", owner: "MR", color: "#d4b8a8", days: 1, tags: ["Warm"] },
|
||||
]},
|
||||
{ name: "Qualified", total: "$365K", tone: "var(--accent)", deals: [
|
||||
{ co: "Northstar", title: "Carrier API", val: "$148K", owner: "MR", color: "#c8e8a8", days: 5, tags: ["Champion"] },
|
||||
{ co: "Kestrel", title: "Renewal + seats", val: "$120K", owner: "TR", color: "#a8c8e8", days: 12 },
|
||||
]},
|
||||
{ name: "Proposal", total: "$280K", tone: "var(--warn)", deals: [
|
||||
{ co: "Halcyon", title: "Pro renewal", val: "$180K", owner: "DP", color: "#c8a8e8", days: 2, tags: ["Sent"] },
|
||||
{ co: "Acme", title: "Edge SDK pilot", val: "$24K", owner: "MR", color: "#e8c8a8", days: 6 },
|
||||
]},
|
||||
{ name: "Negotiation", total: "$148K", tone: "#f59e0b", deals: [
|
||||
{ co: "Acme Robotics", title: "Renewal '26", val: "$148K", owner: "MR", color: "#e8a87c", days: 1, tags: ["Hot", "Closing"] },
|
||||
]},
|
||||
{ name: "Won", total: "$96K", tone: "var(--success)", deals: [
|
||||
{ co: "Lowell Works", title: "Annual plan", val: "$96K", owner: "TR", color: "#a8c8e8", days: 0, tags: ["Closed"] },
|
||||
]},
|
||||
];
|
||||
return (
|
||||
<CRMShell active="deals">
|
||||
<PageBar title="Deals" sub="12 active · $1.24M open pipeline"
|
||||
actions={<>
|
||||
<Button variant="secondary" leadingIcon={<Icon name="bar" size={13}/>}>Forecast</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>New deal</Button>
|
||||
</>}/>
|
||||
<div style={{ padding: "12px 28px", borderBottom: "1px solid var(--divider)", display: "flex", alignItems: "center", gap: 10, background: "var(--surface)" }}>
|
||||
<Input placeholder="Search deals" leadingIcon={<Icon name="search" size={13}/>} style={{ width: 240, padding: "6px 10px" }}/>
|
||||
{["Owner", "Close date", "Value"].map(f => (
|
||||
<div key={f} style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 10px", border: "1px dashed var(--border)", borderRadius: "var(--radius-sm)", fontSize: 12, color: "var(--text-2)", cursor: "pointer" }}>{f}<Icon name="chevDown" size={11}/></div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: "auto", padding: 20, background: "var(--bg)" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(5, minmax(240px, 1fr))", gap: 14, height: "100%", minWidth: "max-content" }}>
|
||||
{columns.map(col => (
|
||||
<div key={col.name} style={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: "0 4px 10px" }}>
|
||||
<span style={{ width: 8, height: 8, borderRadius: "50%", background: col.tone }}/>
|
||||
<span style={{ fontSize: 13, fontWeight: 600 }}>{col.name}</span>
|
||||
<span style={{ fontSize: 12, color: "var(--text-3)" }}>{col.deals.length}</span>
|
||||
<span style={{ flex: 1 }}/>
|
||||
<span style={{ fontSize: 12, color: "var(--text-2)", fontVariantNumeric: "tabular-nums" }}>{col.total}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 10, flex: 1, overflowY: "auto", paddingBottom: 8 }}>
|
||||
{col.deals.map((d, i) => (
|
||||
<div key={i} style={{ padding: 14, borderRadius: "var(--radius)", background: "var(--surface)", border: "1px solid var(--border)", boxShadow: "var(--shadow-sm)", cursor: "grab" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
|
||||
<span style={{ width: 22, height: 22, borderRadius: 6, background: "var(--surface-alt)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: "var(--text-2)" }}>{d.co[0]}</span>
|
||||
<span style={{ fontSize: 12, color: "var(--text-2)", flex: 1, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{d.co}</span>
|
||||
<IconButton name="more" size="sm" label="More"/>
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, letterSpacing: "-0.01em" }}>{d.title}</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 600, marginTop: 6, fontVariantNumeric: "tabular-nums" }}>{d.val}</div>
|
||||
{d.tags && <div style={{ display: "flex", gap: 6, marginTop: 10, flexWrap: "wrap" }}>
|
||||
{d.tags.map(t => <Badge key={t} tone={t === "Hot" || t === "Closing" ? "danger" : t === "Closed" ? "success" : "neutral"}>{t}</Badge>)}
|
||||
</div>}
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 12, paddingTop: 10, borderTop: "1px solid var(--divider)" }}>
|
||||
<Avatar name={d.owner} color={d.color} size={22}/>
|
||||
<span style={{ fontSize: 11, color: d.days <= 1 ? "var(--danger)" : "var(--text-3)" }}>
|
||||
{d.days === 0 ? "Closed" : d.days === 1 ? "Closes today" : `${d.days}d to close`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button style={{ padding: "8px", borderRadius: "var(--radius)", border: "1px dashed var(--border)", background: "transparent", color: "var(--text-3)", fontSize: 12, fontFamily: "var(--font-sans)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>
|
||||
<Icon name="plus" size={12}/> Add deal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CRMShell>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 5 · INBOX
|
||||
// ============================================================
|
||||
const CRMInbox = () => {
|
||||
const threads = [
|
||||
["IT", "#e8a87c", "Iris Tanaka", "Acme Robotics", "Re: Renewal terms — forwarding to Marco to start paper.", "10:42", 1, true],
|
||||
["SK", "#c8e8a8", "Sun Kim", "Northstar", "Could we move the demo to Thursday?", "9:18", 1, false],
|
||||
["DP", "#a8c8e8", "Devi Patel", "Internal", "Closed Halcyon! 🎉 Logging it now.", "Tue", 0, false],
|
||||
["PN", "#c8a8e8", "Priya Nair", "Halcyon", "Thanks for the deck — a few questions inside.", "Mon", 0, false],
|
||||
["ML", "#e8c8a8", "Marco Lindqvist", "Kestrel", "Procurement needs the security packet.", "May 28", 0, false],
|
||||
];
|
||||
return (
|
||||
<CRMShell active="inbox">
|
||||
<div style={{ display: "grid", gridTemplateColumns: "340px 1fr", flex: 1, overflow: "hidden" }}>
|
||||
{/* List */}
|
||||
<div style={{ borderRight: "1px solid var(--divider)", display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||
<div style={{ padding: "16px 18px 10px" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10 }}>
|
||||
<h1 style={{ margin: 0, fontSize: 20, fontWeight: 600 }}>Inbox</h1>
|
||||
<Button size="sm" leadingIcon={<Icon name="plus" size={12}/>}>Compose</Button>
|
||||
</div>
|
||||
<Input placeholder="Search messages" leadingIcon={<Icon name="search" size={13}/>}/>
|
||||
</div>
|
||||
<div style={{ padding: "0 12px 6px", display: "flex", gap: 6 }}>
|
||||
{["All", "Unread", "Assigned", "Deals"].map((t, i) => (
|
||||
<span key={t} style={{ padding: "5px 10px", borderRadius: 999, fontSize: 12, fontWeight: 500, cursor: "pointer", background: i === 0 ? "var(--text)" : "transparent", color: i === 0 ? "var(--bg)" : "var(--text-2)" }}>{t}</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "8px 8px" }}>
|
||||
{threads.map((th, i) => (
|
||||
<div key={i} style={{ display: "flex", gap: 12, padding: 12, borderRadius: "var(--radius)", cursor: "pointer", marginBottom: 2, background: th[7] ? "var(--surface)" : "transparent", boxShadow: th[7] ? "var(--shadow-sm)" : "none" }}>
|
||||
<Avatar name={th[2]} color={th[1]} size={38}/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: 6 }}>
|
||||
<span style={{ fontSize: 13, fontWeight: th[6] ? 700 : 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{th[2]}</span>
|
||||
<span style={{ fontSize: 11, color: "var(--text-3)", flexShrink: 0 }}>{th[5]}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: "var(--accent)", fontWeight: 500, marginTop: 1 }}>{th[3]}</div>
|
||||
<div style={{ fontSize: 12, color: th[6] ? "var(--text)" : "var(--text-2)", marginTop: 2, display: "-webkit-box", WebkitLineClamp: 1, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{th[4]}</div>
|
||||
</div>
|
||||
{th[6] > 0 && <span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--accent)", alignSelf: "center", flexShrink: 0 }}/>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Conversation */}
|
||||
<div style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||
<div style={{ padding: "14px 24px", borderBottom: "1px solid var(--divider)", display: "flex", alignItems: "center", gap: 14, background: "var(--surface)" }}>
|
||||
<Avatar name="Iris Tanaka" color="#e8a87c" size={40}/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 600 }}>Iris Tanaka</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)" }}>iris@acme.io · Head of Engineering</div>
|
||||
</div>
|
||||
<Button variant="secondary" size="sm">Open record</Button>
|
||||
<IconButton name="more" variant="secondary" label="More"/>
|
||||
</div>
|
||||
{/* Linked deal */}
|
||||
<div style={{ padding: "12px 24px 0" }}>
|
||||
<div style={{ display: "flex", gap: 12, padding: 12, borderRadius: "var(--radius)", background: "var(--surface-2)", border: "1px solid var(--border)", alignItems: "center" }}>
|
||||
<span style={{ width: 36, height: 36, borderRadius: 8, background: "linear-gradient(135deg, #ff8a3a, #f43f5e)", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 700 }}>A</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Acme — Renewal '26</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>Negotiation · $148,000 · closes Jun 12</div>
|
||||
</div>
|
||||
<Badge tone="warn" dot>Closing soon</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* Messages */}
|
||||
<div style={{ flex: 1, overflowY: "auto", padding: "20px 24px" }}>
|
||||
<div style={{ textAlign: "center", fontSize: 11, color: "var(--text-3)", margin: "0 0 16px" }}>Today</div>
|
||||
{[
|
||||
[false, "Hi Mira — the team reviewed the renewal terms and they look great. I'm forwarding to Marco in procurement to start the paperwork.", "10:14"],
|
||||
[true, "That's wonderful news, Iris. I'll send Marco the order form today. Anything he needs from our side to move quickly?", "10:28"],
|
||||
[false, "Just the security packet (SOC 2 + the SSO/SCIM details). If you can get that over, we should be able to close by the 12th.", "10:42"],
|
||||
].map(([mine, body, time], i) => (
|
||||
<div key={i} style={{ display: "flex", flexDirection: mine ? "row-reverse" : "row", gap: 10, alignItems: "flex-end", marginBottom: 12 }}>
|
||||
{!mine && <Avatar name="Iris Tanaka" color="#e8a87c" size={28}/>}
|
||||
<div style={{ maxWidth: "62%" }}>
|
||||
<div style={{ padding: "10px 14px", borderRadius: 16, background: mine ? "var(--text)" : "var(--surface-2)", color: mine ? "var(--bg)" : "var(--text)", fontSize: 13, lineHeight: 1.45, borderBottomRightRadius: mine ? 4 : 16, borderBottomLeftRadius: mine ? 16 : 4, boxShadow: "var(--shadow-sm)" }}>{body}</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)", marginTop: 4, textAlign: mine ? "right" : "left" }}>{time}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Composer */}
|
||||
<div style={{ padding: 16, borderTop: "1px solid var(--divider)" }}>
|
||||
<div style={{ display: "flex", alignItems: "flex-end", gap: 10, padding: 8, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--border)" }}>
|
||||
<IconButton name="plus" size="sm" variant="ghost" label="Attach"/>
|
||||
<textarea placeholder="Reply to Iris…" rows="1" style={{ flex: 1, border: "none", outline: "none", background: "transparent", fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text)", resize: "none", padding: "6px 0" }}/>
|
||||
<Button size="sm">Send</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CRMShell>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 6 · REPORTS
|
||||
// ============================================================
|
||||
const CRMReports = () => {
|
||||
const months = ["Dec","Jan","Feb","Mar","Apr","May"];
|
||||
const won = [180, 220, 195, 260, 240, 284];
|
||||
const goal = 250;
|
||||
const maxV = 320;
|
||||
return (
|
||||
<CRMShell active="reports">
|
||||
<PageBar title="Reports" sub="Sales performance · last 6 months"
|
||||
actions={<>
|
||||
<Select value="Last 6 months"/>
|
||||
<Button variant="secondary">Export</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>New report</Button>
|
||||
</>}/>
|
||||
<Scroll>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 14, marginBottom: 20 }}>
|
||||
<Stat label="Bookings · MTD" value="$284K" delta="18%" up spark={sparkUp}/>
|
||||
<Stat label="New pipeline" value="$612K" delta="9%" up spark={sparkUp}/>
|
||||
<Stat label="Avg deal size" value="$84K" delta="2%" up={false} spark={sparkDn}/>
|
||||
<Stat label="Sales cycle" value="34d" delta="3d" up spark={sparkUp}/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 20 }}>
|
||||
{/* Bookings vs goal */}
|
||||
<Card padding={24}>
|
||||
<CardHeader title="Bookings vs goal" subtitle="Closed-won by month"
|
||||
action={<Tabs variant="pill" items={[{ label: "Monthly" }, { label: "Quarterly" }]} active="Monthly"/>}/>
|
||||
<div style={{ position: "relative", height: 220, marginTop: 8 }}>
|
||||
{/* goal line */}
|
||||
<div style={{ position: "absolute", left: 0, right: 0, bottom: `${(goal / maxV) * 100}%`, borderTop: "2px dashed var(--accent)", zIndex: 1 }}>
|
||||
<span style={{ position: "absolute", right: 0, top: -18, fontSize: 11, color: "var(--accent)", fontWeight: 600, background: "var(--surface)", padding: "0 4px" }}>Goal ${goal}K</span>
|
||||
</div>
|
||||
<div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "flex-end", gap: 18, paddingRight: 4 }}>
|
||||
{won.map((v, i) => (
|
||||
<div key={i} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-end", height: "100%", gap: 8 }}>
|
||||
<div style={{ width: "100%", maxWidth: 44, height: `${(v / maxV) * 100}%`, borderRadius: "6px 6px 0 0", background: v >= goal ? "linear-gradient(180deg, var(--success), color-mix(in srgb, var(--success) 50%, transparent))" : "linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 45%, transparent))" }}/>
|
||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>{months[i]}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Leaderboard */}
|
||||
<Card padding={0}>
|
||||
<CardHeader title="Team leaderboard" subtitle="This month"
|
||||
style={{ padding: "16px 20px", margin: 0, borderBottom: "1px solid var(--divider)" }}/>
|
||||
<div style={{ padding: "6px 16px 12px" }}>
|
||||
{[
|
||||
["MR", "#d4b8a8", "Mira Reyes", "$124K", 100],
|
||||
["DP", "#a8c8e8", "Devi Patel", "$86K", 70],
|
||||
["TR", "#c8e8a8", "Theo Roux", "$62K", 50],
|
||||
["SK", "#e8a87c", "Sun Ortiz", "$48K", 39],
|
||||
].map(([i, c, n, v, p], idx) => (
|
||||
<div key={idx} style={{ display: "grid", gridTemplateColumns: "20px 28px 1fr auto", gap: 10, alignItems: "center", padding: "9px 0", borderBottom: idx < 3 ? "1px solid var(--divider)" : "none" }}>
|
||||
<span style={{ fontSize: 12, color: "var(--text-3)" }}>#{idx + 1}</span>
|
||||
<Avatar name={n} color={c} size={28}/>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500 }}>{n}</div>
|
||||
<div style={{ height: 3, borderRadius: 2, background: "var(--surface-alt)", overflow: "hidden", marginTop: 4 }}>
|
||||
<div style={{ width: `${p}%`, height: "100%", background: "var(--accent)" }}/>
|
||||
</div>
|
||||
</div>
|
||||
<span style={{ fontSize: 13, fontWeight: 600 }}>{v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Scroll>
|
||||
</CRMShell>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 7 · SETTINGS · members (admin)
|
||||
// ============================================================
|
||||
const CRMSettings = () => {
|
||||
const members = [
|
||||
{ id: 1, name: "Mira Reyes", email: "mira@northwind.io", color: "#d4b8a8", role: "Owner", status: "Active", last: "now" },
|
||||
{ id: 2, name: "Theo Roux", email: "theo@northwind.io", color: "#c8e8a8", role: "Admin", status: "Active", last: "12 min" },
|
||||
{ id: 3, name: "Devi Patel", email: "devi@northwind.io", color: "#a8c8e8", role: "Admin", status: "Active", last: "1 hour" },
|
||||
{ id: 4, name: "Sun Ortiz", email: "sun@northwind.io", color: "#e8a87c", role: "Member", status: "Active", last: "today" },
|
||||
{ id: 5, name: "Linnea Berg", email: "linnea@northwind.io", color: "#c8a8e8", role: "Member", status: "Invited", last: "—" },
|
||||
{ id: 6, name: "Jamal Frost", email: "jamal@partner.co", color: "#a8e8c8", role: "Guest", status: "Active", last: "3 days" },
|
||||
];
|
||||
const roleTone = { Owner: "accent", Admin: "info", Member: "success", Guest: "neutral" };
|
||||
return (
|
||||
<CRMShell active="settings">
|
||||
<div style={{ display: "grid", gridTemplateColumns: "200px 1fr", flex: 1, overflow: "hidden" }}>
|
||||
{/* Settings subnav */}
|
||||
<div style={{ borderRight: "1px solid var(--divider)", padding: "18px 12px", overflowY: "auto" }}>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 500, padding: "0 10px 8px" }}>Workspace</div>
|
||||
{["General", "Members", "Roles", "Pipeline stages", "Integrations", "Billing", "API"].map((s, i) => (
|
||||
<div key={s} style={{ padding: "7px 10px", borderRadius: "var(--radius-sm)", fontSize: 13, cursor: "pointer", marginBottom: 2, color: i === 1 ? "var(--text)" : "var(--text-2)", fontWeight: i === 1 ? 500 : 400, background: i === 1 ? "var(--surface)" : "transparent", boxShadow: i === 1 ? "var(--shadow-sm)" : "none" }}>{s}</div>
|
||||
))}
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 500, padding: "16px 10px 8px" }}>Personal</div>
|
||||
{["Profile", "Notifications", "Sessions"].map(s => (
|
||||
<div key={s} style={{ padding: "7px 10px", borderRadius: "var(--radius-sm)", fontSize: 13, color: "var(--text-2)", cursor: "pointer", marginBottom: 2 }}>{s}</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Members panel */}
|
||||
<div style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||
<PageBar breadcrumb="Settings" title="Members"
|
||||
sub="Manage who can access the Northwind workspace."
|
||||
actions={<>
|
||||
<Button variant="secondary">Export CSV</Button>
|
||||
<Button leadingIcon={<Icon name="plus" size={13}/>}>Invite people</Button>
|
||||
</>}/>
|
||||
<div style={{ padding: "12px 28px", borderBottom: "1px solid var(--divider)", display: "flex", alignItems: "center", gap: 10, background: "var(--surface)" }}>
|
||||
<Input placeholder="Search members" leadingIcon={<Icon name="search" size={13}/>} style={{ width: 260, padding: "6px 10px" }}/>
|
||||
<div style={{ flex: 1 }}/>
|
||||
<span style={{ fontSize: 12, color: "var(--text-2)" }}><b style={{ color: "var(--text)" }}>6</b> members · 1 invited</span>
|
||||
</div>
|
||||
<Scroll pad={20}>
|
||||
<Table
|
||||
columns={[
|
||||
{ key: "name", label: "Name", render: r => (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<Avatar name={r.name} color={r.color} size={28}/>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{r.name}</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{r.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
)},
|
||||
{ key: "role", label: "Role", render: r => (
|
||||
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 9px", borderRadius: "var(--radius-sm)", background: "var(--accent-soft)", color: "var(--accent)", fontSize: 12, fontWeight: 500, cursor: "pointer" }}>
|
||||
{r.role}<Icon name="chevDown" size={11}/>
|
||||
</span>
|
||||
)},
|
||||
{ key: "status", label: "Status", render: r => (
|
||||
<Badge dot tone={r.status === "Active" ? "success" : "warn"}>{r.status}</Badge>
|
||||
)},
|
||||
{ key: "last", label: "Last active" },
|
||||
{ key: "act", label: "", align: "right", width: 40, render: () => <IconButton name="more" size="sm" label="More"/> },
|
||||
]}
|
||||
rows={members}
|
||||
/>
|
||||
<div style={{ marginTop: 18 }}>
|
||||
<Banner tone="warn" title="1 invitation pending"
|
||||
action={<Button size="sm" variant="secondary">Resend</Button>}>
|
||||
linnea@northwind.io hasn't accepted yet — sent 3 days ago.
|
||||
</Banner>
|
||||
</div>
|
||||
</Scroll>
|
||||
</div>
|
||||
</div>
|
||||
</CRMShell>
|
||||
);
|
||||
};
|
||||
|
||||
Object.assign(window, {
|
||||
CRMHome, CRMPeople, CRMRecord, CRMPipeline, CRMInbox, CRMReports, CRMSettings,
|
||||
});
|
||||
Reference in New Issue
Block a user