User Onboarding

Build interactive onboarding tours with React Joyride

User Onboarding

Eden Stack includes a complete onboarding system using React Joyride with custom tooltips, Lucide icons, and confetti celebrations.

Features

  • Server-side status check - Onboarding status loaded with auth, no client-side flash
  • Custom tooltips - shadcn/ui Card with Framer Motion animations
  • Icon support - Lucide icons in each step
  • Confetti celebration - Fires when users complete the tour
  • Persistent tracking - Completion stored in database per user

How It Works

Onboarding status is checked server-side in the _authenticated route, alongside subscription status:

// Route context includes onboarding data
const { user, subscription, onboarding } = Route.useRouteContext();
 
// Check if user needs onboarding
if (onboarding.needsOnboarding) {
  // Show tour
}

Creating Tour Steps

Define steps in apps/web/src/components/onboarding/{page}-tour-steps.ts:

import type { Step } from "react-joyride";
import { LayoutDashboard, Zap, Compass } from "lucide-react";
import type { OnboardingStepData } from "./onboarding-card";
 
export const myTourSteps: (Step & { data?: OnboardingStepData })[] = [
  {
    target: '[data-tour="welcome-card"]',
    title: "Welcome",
    content: "Your dashboard overview.",
    placement: "bottom",
    disableBeacon: true,
    data: { icon: LayoutDashboard },
  },
  {
    target: '[data-tour="quick-actions"]',
    title: "Quick Actions",
    content: "Create projects or start conversations.",
    placement: "left",
    data: { icon: Zap },
  },
];

Adding Tour Targets

Add data-tour attributes to elements you want to highlight:

<Card data-tour="welcome-card">
  <CardHeader>Welcome!</CardHeader>
</Card>
 
<Button data-tour="quick-actions">New Project</Button>

Using the Tour Component

import { DashboardTour } from "@/components/onboarding";
 
function DashboardPage() {
  const { onboarding } = Route.useRouteContext();
  
  return (
    <div>
      <DashboardTour needsOnboarding={onboarding.needsOnboarding} />
      {/* Page content */}
    </div>
  );
}

Database Schema

The users table includes an onboardingCompletedAt timestamp:

export const users = pgTable("users", {
  // ...other fields
  onboardingCompletedAt: timestamp("onboarding_completed_at"),
});

API Endpoints

EndpointMethodDescription
/api/onboarding/statusGETCheck if user completed onboarding
/api/onboarding/completePOSTMark onboarding as complete

Customization

Custom Icons

Pass any Lucide icon in the step's data property:

import { Rocket, Settings, Sparkles } from "lucide-react";
 
{
  target: '[data-tour="upgrade"]',
  title: "Upgrade",
  content: "Unlock premium features.",
  data: { icon: Sparkles },
}

Multiple Tours

For feature-specific tours, add additional timestamp fields:

// In schema
dashboardTourCompletedAt: timestamp("dashboard_tour_completed_at"),
projectsTourCompletedAt: timestamp("projects_tour_completed_at"),

Testing

Reset onboarding to test the tour again:

UPDATE users 
SET onboarding_completed_at = NULL 
WHERE email = 'your@email.com';

Or use Drizzle Studio: bun run db:studio

Full documentation for Eden Stack users

This documentation is exclusively available to Eden Stack customers. Already purchased? Log in to access the full content.