// SP14 / M14 — Onboarding wizard // Multi-step first-run experience: welcome → workspace → invites → integrations // → sample data → done + first-run checklist. const { useState: useStateOB, useEffect: useEffectOB } = React; const Onboarding = ({ setPage }) => { const [step, setStep] = React.useState(0); const [data, setData] = React.useState({ role: 'founder', workspaceName: 'My Studio', subdomain: 'mystudio', brandColor: '#d4ff3a', logo: null, invites: [ { email: '', role: 'Editor' }, { email: '', role: 'Editor' }, ], integrations: { stripe:false, resend:true, posthog:false, slack:false, sentry:false }, seed: 'studio', }); const steps = [ { id:'welcome', label:'Welcome', sub:'Tell us who you are' }, { id:'workspace', label:'Workspace', sub:'Name, URL, brand' }, { id:'invite', label:'Invite', sub:'Add your team' }, { id:'integrate', label:'Integrate', sub:'Connect what you use' }, { id:'seed', label:'Sample data', sub:'Pick a starting point' }, { id:'done', label:'Ready', sub:"You're all set" }, ]; const update = (patch) => setData(d => ({ ...d, ...patch })); const next = () => setStep(s => Math.min(s+1, steps.length-1)); const back = () => setStep(s => Math.max(s-1, 0)); return (
{step === 0 && } {step === 1 && } {step === 2 && } {step === 3 && } {step === 4 && } {step === 5 && }
{step > 0 && step < 5 && }
); }; // ───────────────────────────────────────────────────────────────── // HEADER WITH STEPPER const OBHeader = ({ step, steps, setPage }) => (
{Icon.atom()}
SolidAtoms
SETUP · {steps.length} STEPS
{/* Stepper */}
{steps.map((s, i) => { const done = i < step; const active = i === step; return (
step ? 0.5 : 1}}> {done ? '✓' : i+1}
{s.label}
{i < steps.length-1 &&
} ); })}
); // ───────────────────────────────────────────────────────────────── // FOOTER const OBFooter = ({ step, steps, back, next }) => ( ); // ───────────────────────────────────────────────────────────────── // STEP 0 — WELCOME const OBWelcome = ({ data, update, next }) => { const roles = [ {id:'founder',t:'Founder / solo',d:'I run my own studio. Just me, maybe a contractor.',c:'#d4ff3a',v:'Most flexible setup'}, {id:'agency',t:'Agency / studio',d:'We\'re a small team building for clients.',c:'#7c9bff',v:'Team workflows + client portal'}, {id:'inhouse',t:'In-house team',d:'Marketing or engineering team at a larger company.',c:'#ff8a3a',v:'Approval workflows + audit log'}, {id:'just-looking',t:'Just exploring',d:'Kicking the tires. Show me what this thing does.',c:'#a78bfa',v:'Demo workspace with sample data'}, ]; return (
Welcome aboard

Let's set up your workspace.

Five quick steps. Takes about two minutes. Nothing you choose here is permanent — you can change everything later.

First, what brings you here?
{roles.map(r => ( ))}
); }; // ───────────────────────────────────────────────────────────────── // STEP 1 — WORKSPACE const OBWorkspace = ({ data, update }) => { const colorOpts = ['#d4ff3a','#7c9bff','#ff8a3a','#a78bfa','#7ee787','#22d3ee','#f472b6','#fde047']; return (
{/* Left: form */}
Step 2 · Workspace identity

What should we call your workspace?

This is what your team sees in the sidebar and what shows up on shared links. You can change it any time.

Workspace name update({workspaceName:e.target.value})} placeholder="Acme Studio" style={{width:'100%',padding:'14px 16px',borderRadius:10,background:'var(--bg-3)',border:'1px solid var(--line-2)',color:'var(--ink)',fontSize:18,fontWeight:500,fontFamily:'var(--sans)',outline:'none',letterSpacing:'-0.005em'}}/>
Workspace URL
solidatoms.com/ update({subdomain:e.target.value.toLowerCase().replace(/[^a-z0-9-]/g,'')})} placeholder="mystudio" style={{flex:1,padding:'14px 16px 14px 0',background:'transparent',border:0,color:'var(--ink)',fontSize:14,fontFamily:'var(--mono)',outline:'none'}}/> ● AVAILABLE
Logo
{data.workspaceName ? data.workspaceName.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase() : '✦'}
SVG OR PNG · 200×200+ · WE'LL USE INITIALS UNTIL YOU UPLOAD
Brand color
{colorOpts.map(c => ( ))}
{/* Right: live preview */}
● LIVE PREVIEW · ADMIN SIDEBAR
{/* Mock admin window */}
solidatoms.com/{data.subdomain || 'workspace'}
Welcome to {data.workspaceName || 'your workspace'}
URL: solidatoms.com/{data.subdomain || 'workspace'}
); }; // ───────────────────────────────────────────────────────────────── // STEP 2 — INVITE TEAM const OBInvite = ({ data, update }) => { const updateInvite = (i, patch) => { const inv = [...data.invites]; inv[i] = { ...inv[i], ...patch }; update({ invites: inv }); }; const addInvite = () => update({ invites: [...data.invites, { email:'', role:'Editor' }] }); const removeInvite = (i) => update({ invites: data.invites.filter((_,idx)=>idx!==i) }); const roles = ['Admin','Editor','Writer','Reviewer']; return (
Step 3 · Invite your team

Who else needs access?

Send invite emails now or skip this step — you can always invite people later from Team & roles. Each invite uses 1 seat.

{/* Bulk paste */}
Inviting a whole team? Paste a list of emails separated by commas or new lines.
{/* Invites */}
{data.invites.map((inv, i) => (
updateInvite(i,{email:e.target.value})} placeholder="teammate@company.com" style={{padding:'12px 14px',borderRadius:8,background:'var(--bg-3)',border:'1px solid var(--line-2)',color:'var(--ink)',fontSize:14,fontFamily:'var(--sans)',outline:'none'}}/>
))}
{/* Role legend */}
What each role can do
{[ ['Admin','Manage content, team, billing'], ['Editor','Write, edit, publish'], ['Writer','Drafts only, no publish'], ['Reviewer','Read + comment'], ].map(([r,d]) => (
{r}
{d}
))}
); }; // ───────────────────────────────────────────────────────────────── // STEP 3 — INTEGRATIONS const OBIntegrate = ({ data, update }) => { const toggle = (key) => update({ integrations: { ...data.integrations, [key]: !data.integrations[key] } }); const integrations = [ {id:'resend',n:'Resend',d:'Send transactional + marketing email',c:'#000',recommend:true,why:'Powers email studio'}, {id:'stripe',n:'Stripe',d:'Accept payments + manage subscriptions',c:'#635bff',recommend:true,why:'For pricing + invoices'}, {id:'posthog',n:'PostHog',d:'Product analytics, funnels, session replay',c:'#1d4aff',recommend:false}, {id:'slack',n:'Slack',d:'Notifications for new leads + reviews',c:'#4a154b',recommend:false}, {id:'sentry',n:'Sentry',d:'Error monitoring + performance',c:'#362d59',recommend:false}, {id:'github',n:'GitHub',d:'Sync deployments + PR previews',c:'#181717',recommend:false}, {id:'linear',n:'Linear',d:'Mirror issues from project mgmt',c:'#5e6ad2',recommend:false}, {id:'cal',n:'Cal.com',d:'Book intro calls from your site',c:'#0a0a0a',recommend:false}, ]; return (
Step 4 · Connect your tools

What do you already use?

Connect now and we'll pre-wire the integration. Skip and you can add any of these from Settings later — nothing's locked in.

{integrations.map(i => { const on = data.integrations[i.id]; return (
{i.n[0]}
{i.n} {i.recommend && Recommended}
{i.d}
{on && i.why &&
● {i.why}
}
); })}
● {Object.values(data.integrations).filter(Boolean).length} OF {integrations.length} CONNECTED · ALL OPTIONAL
); }; // ───────────────────────────────────────────────────────────────── // STEP 4 — SAMPLE DATA const OBSeed = ({ data, update }) => { const seeds = [ {id:'studio',t:'Studio starter',d:'Empty workspace with a few example projects so you can see how everything fits together.',includes:['3 sample projects','6 services','5 team profiles','12 blog drafts','42 sample tools'],c:'#d4ff3a'}, {id:'agency',t:'Agency template',d:'Pre-loaded with agency-shaped content. Just replace names + links.',includes:['Agency services scaffold','Case study template','Client portal mock','Welcome email sequence'],c:'#7c9bff'}, {id:'blank',t:'Blank slate',d:'Start completely empty. Best if you know what you want.',includes:['Just the design system','Empty collections','Default templates','Empty media library'],c:'#a78bfa'}, ]; return (
Step 5 · Starting point

Want to start with sample data?

Pre-loaded content makes it easier to see how SolidAtoms works. Everything's safe to delete or modify.

{seeds.map(s => { const sel = data.seed === s.id; return ( ); })}
); }; // ───────────────────────────────────────────────────────────────── // STEP 5 — DONE + FIRST-RUN CHECKLIST const OBDone = ({ data, setPage }) => { const [tasks, setTasks] = React.useState({ profile:false, post:false, project:false, dns:false, domain:false, share:false }); const completed = Object.values(tasks).filter(Boolean).length; const total = Object.keys(tasks).length; return (
{/* Hero */}
You're all set

Welcome to {data.workspaceName || 'your studio'}.

{data.invites.filter(i=>i.email).length > 0 ? `We sent invites to ${data.invites.filter(i=>i.email).length} teammate${data.invites.filter(i=>i.email).length > 1 ? 's' : ''}. They'll get a welcome email from your shiny new Email Studio.` : 'Your workspace is ready. Here are a few things you can do next.'}

{/* Checklist */}

First-run checklist

A few things worth doing in your first week.
{completed} OF {total} DONE
{[ {id:'profile',t:'Set up your founder profile',d:'Add your bio, links, and headshot.',time:'2 min'}, {id:'post',t:'Publish your first blog post',d:'Write something short. Anything.',time:'15 min'}, {id:'project',t:'Import a past project',d:'Drag in a screenshot, fill in 4 fields.',time:'5 min'}, {id:'dns',t:'Verify your sending domain',d:'Adds DKIM + SPF for email deliverability.',time:'10 min'}, {id:'domain',t:'Connect your custom domain',d:'Replace solidatoms.com/yourname with your own URL.',time:'5 min'}, {id:'share',t:'Share with the team',d:'Drop the workspace URL in Slack.',time:'30 sec'}, ].map(t => ( ))}
{/* What's wired up */}
● WORKSPACE LIVE
{data.workspaceName}
solidatoms.com/{data.subdomain}
● INTEGRATIONS
{Object.values(data.integrations).filter(Boolean).length} connected
{Object.keys(data.integrations).filter(k=>data.integrations[k]).join(' · ').toUpperCase() || 'NONE YET'}
● WELCOME EMAIL
Queued · sends in 2 min
VIA EMAIL STUDIO · M13
{/* Next steps */}
Stuck on something? Hit ⌘K anywhere to open the command palette, or message us at hi@solidatoms.com — a human replies inside 24h.
); }; window.Onboarding = Onboarding;