A multi-step onboarding modal for new Sentra users. Built with Next.js 16, React 19, Tailwind CSS 4, and Radix UI primitives.
pnpm install
pnpm devNavigate to http://localhost:3000/onboarding to see the flow.
components/onboarding/
├── onboarding-modal.tsx # Main orchestrator - manages step state & routing
├── step-asset-panel.tsx # Left panel with contextual visuals per step
└── steps/
├── auth-step.tsx # Email/password or Google OAuth
├── verification-step.tsx # Email verification (auto-advances)
├── permissions-step.tsx # Mic + camera permissions
├── product-modes-step.tsx# Bot / Stealth / In-person mode selection
├── features-overview-step.tsx # Feature carousel (4 sub-steps)
├── connections-step.tsx # Calendar, Gmail, Slack, Linear integrations
├── workspace-step.tsx # Team workspace creation (work emails only)
└── trial-step.tsx # Payment + success state
| # | Step | Required | Notes |
|---|---|---|---|
| 1 | Auth | ✅ | Google OAuth or email/password signup |
| 2 | Verification | ✅ | Auto-advances after simulated verification |
| 3 | Permissions | ✅ | Both mic + camera must be granted |
| 4 | Product Modes | ✅ | Choose default: Bot, Stealth, or In-person |
| 5 | Features Overview | ✅ | 4-step carousel explaining core features |
| 6 | Connections | Calendar/Gmail recommended; Slack/Linear optional | |
| 7 | Workspace | 🔀 | Only shown for work emails (not gmail.com, etc.) |
| 8 | Trial | ✅ | Payment form → success state → redirect |
Work Email Detection (onboarding-modal.tsx:46-59)
function isWorkEmail(email: string): boolean {
const personalDomains = [
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com",
"icloud.com", "aol.com", "protonmail.com", "mail.com"
]
const domain = email.split("@")[1]?.toLowerCase()
return domain ? !personalDomains.includes(domain) : false
}- Work email → includes Workspace step, shows Slack/Linear integrations
- Personal email → skips Workspace step, locks Slack/Linear behind "Available with workspaces"
Purpose: Sign up or sign in with Google or email/password.
Props:
interface AuthStepProps {
onComplete: (email: string) => void
}Implementation Notes:
- Currently mocks Google OAuth with hardcoded email
- Toggle between sign-up and sign-in modes
- Form validation is minimal (just checks non-empty)
TODO for production:
- Integrate real OAuth provider (Google, etc.)
- Add proper form validation with Zod
- Password strength requirements
- Error handling for failed auth
Purpose: Wait for email verification link click.
Props:
interface VerificationStepProps {
email: string
onComplete: () => void
}Implementation Notes:
- Auto-advances through states:
sent→verifying→verified - Timers simulate the verification flow (2s → 1.5s → 1s auto-advance)
- "Resend email" button shown in
sentstate
TODO for production:
- Real email verification via backend
- Poll or websocket for verification status
- Rate limiting on resend
- Magic link alternative
Purpose: Request microphone and camera permissions for in-person recording.
Props:
interface PermissionsStepProps {
onComplete: () => void
}Implementation Notes:
- Both permissions must be granted to continue
- Currently mocks permission granting with 500ms delay
TODO for production:
- Use
navigator.mediaDevices.getUserMedia()for real permissions - Handle denied permissions gracefully
- Store permission state in user preferences
- Show system permission dialog
Purpose: Choose default meeting capture method.
Props:
interface ProductModesStepProps {
onComplete: () => void
onModeChange?: (mode: string) => void
}Modes:
| Mode | ID | Description |
|---|---|---|
| Meeting Bot | bot |
Sentra joins as visible participant (recommended) |
| Stealth Mode | stealth |
Records screen + audio without joining call |
| In-person | inperson |
Device mic/camera for real-world meetings |
Implementation Notes:
onModeChangecallback updates the asset panel visualization- Selection persists via React state (not stored yet)
TODO for production:
- Save preference to user profile
- Show mode-specific setup instructions
Purpose: Carousel walkthrough of 4 core features.
Props:
interface FeaturesOverviewStepProps {
onComplete: () => void
onSubStepChange?: (index: number) => void
}Features (sub-steps):
- Pre-meeting briefs
- Auto to-do extraction
- Autonomous CRM
- In-person recording
Implementation Notes:
- Progress bar at bottom shows position
- Users can click progress dots to jump
- Asset panel updates per sub-step via
onSubStepChange
Purpose: Connect calendar, email, and team tools.
Props:
interface ConnectionsStepProps {
onComplete: () => void
isWorkEmail?: boolean
}Integrations:
| Integration | Priority | Work Email Only |
|---|---|---|
| Google Calendar | ✅ Required | No |
| Gmail | ✅ Required | No |
| Slack | Optional | Yes |
| Linear | Optional | Yes |
Implementation Notes:
- At least one priority integration needed to enable "Continue"
- "Skip for now" always available
- Work email users see Slack/Linear; personal email users see them locked
TODO for production:
- Real OAuth flows for each integration
- Store connection status in database
- Handle disconnection/reconnection
- Add more integrations (Notion, HubSpot, etc.)
Purpose: Create team workspace and invite colleagues.
Props:
interface WorkspaceStepProps {
email: string
onComplete: () => void
}Implementation Notes:
- Only shown for work emails
- Workspace name defaults to email domain (e.g.,
acmefromuser@acme.com) - Invite emails collected via tag input
- Enter key or blur adds email to list
TODO for production:
- Create workspace in backend
- Send invitation emails
- Handle existing workspace (join vs. create)
- Role assignment for invitees
Purpose: Collect payment info and start 14-day free trial.
Props: None (final step)
States:
- Payment form - Card number, expiry, CVC
- Success state - Celebration animation → "Enter Sentra" button
Implementation Notes:
- Card formatting: groups of 4 digits, MM/YY expiry
- Validation: 16 digits, 5 char expiry (MM/YY), 3-4 digit CVC
- Success redirects to
/(main product)
TODO for production:
- Integrate Stripe Elements or similar
- Real payment processing
- Trial tracking in backend
- Handle payment failures
- Add billing address if required
Left panel showing contextual visuals for each step.
Props:
interface StepAssetPanelProps {
currentStep: OnboardingStep
featureSubStep?: number // For features-overview carousel
selectedMode?: string // For product-modes visualization
}Each step has a corresponding asset component:
AuthAsset- Welcome message + feature bulletsVerificationAsset- Security messagingPermissionsAsset- Mic/camera icons + waveformProductModesAsset- Interactive mode visualizationsFeaturesAsset- Feature-specific mock UIsConnectionsAsset- Integration benefitsWorkspaceAsset- Team collaboration messagingTrialAsset- Trial benefits list
--sentra-blue: oklch(0.58 0.13 250); /* Primary brand blue */
--sentra-blue-light: oklch(0.96 0.015 250); /* Light blue background */
--sentra-success: oklch(0.65 0.2 145); /* Success green */
--sentra-success-light: oklch(0.95 0.03 145);Each step has a unique entrance animation:
| Step | Animation Class | Effect |
|---|---|---|
| auth | animate-float-in |
Fade up + scale |
| verification | animate-slide-in-right |
Slide from left |
| permissions | animate-expand-in |
Scale in |
| product-modes | animate-float-in |
Fade up + scale |
| features-overview | animate-reveal-up |
Slide up + blur |
| connections | animate-rotate-in |
Slight rotation |
| workspace | animate-expand-in |
Scale in |
| trial | animate-float-in |
Fade up + scale |
Currently all state lives in OnboardingModal:
const [currentStep, setCurrentStep] = useState<OnboardingStep>("auth")
const [email, setEmail] = useState("")
const [hasWorkEmail, setHasWorkEmail] = useState(false)
const [mounted, setMounted] = useState(false)
const [featureSubStep, setFeatureSubStep] = useState(0)
const [selectedMode, setSelectedMode] = useState("bot")Consider moving to:
- URL-based step tracking (for back button support)
- Zustand or similar for step state
- Persist partial progress to localStorage/backend
- Resume interrupted onboarding
| Endpoint | Purpose | Step |
|---|---|---|
POST /auth/signup |
Create account | Auth |
POST /auth/signin |
Sign in existing | Auth |
POST /auth/google |
Google OAuth | Auth |
POST /auth/verify |
Check verification status | Verification |
POST /auth/resend-verification |
Resend email | Verification |
POST /users/preferences |
Save mode preference | Product Modes |
POST /integrations/:provider/connect |
OAuth start | Connections |
GET /integrations/:provider/callback |
OAuth callback | Connections |
POST /workspaces |
Create workspace | Workspace |
POST /workspaces/:id/invites |
Send invites | Workspace |
POST /billing/subscribe |
Start trial | Trial |
-
Personal email flow (e.g.,
user@gmail.com)- Should skip Workspace step
- Should show Slack/Linear as locked
-
Work email flow (e.g.,
user@acme.com)- Should include Workspace step
- Should show all integrations
-
Permissions denied
- Continue button should stay disabled
-
Skip integrations
- Should allow proceeding without connections
-
Payment validation
- Invalid card formats should be rejected
app/
├── globals.css # Theme + animations
├── layout.tsx # Root layout with ThemeProvider
├── onboarding/
│ └── page.tsx # Onboarding page (renders modal)
└── page.tsx # Home (post-onboarding redirect target)
components/
├── onboarding/ # Onboarding-specific components
├── theme-provider.tsx # next-themes provider
└── ui/ # shadcn/ui components
lib/
└── utils.ts # cn() utility for Tailwind classes
Key packages:
next@16- App router, React Server Componentsreact@19- Latest React with concurrent featurestailwindcss@4- CSS framework@radix-ui/*- Accessible UI primitiveslucide-react- Icon librarynext-themes- Dark mode supportinput-otp- OTP input (for future use)
Reach out to the team if you need clarification on:
- OAuth integration specifics
- Backend API contracts
- Design system tokens
- Animation timing preferences