Tour Manager

Prev Next

Tour Manager

The TourManager class orchestrates guided product tours — step-by-step onboarding and feature discovery flows within the CMS. It wraps NextStepJS with versioned persistence so tours auto-start for new users and stay dismissed for returning users.

How It Works

Image

API

import { TourManager } from '@/lib/internal/TourManager';
import DASHBOARD_TOUR from '@/config/tours/dashboard';

const manager = new TourManager(DASHBOARD_TOUR, 1.1);

manager.shouldAutoStart(); // true if localStorage key is absent (first visit)
manager.getTours(); // returns the Tour[] array for NextStepJS
manager.markCompleted(); // persists 'completed' to localStorage
manager.markSkipped(); // persists 'skipped' to localStorage

Constructor

constructor(tours: Tour[], version: number)
Parameter Type Purpose
tours Tour[] (from nextstepjs) Array of tour step definitions
version number Version number — changing this resets the tour for all users

The storage key is derived as tour:{tourName}:v{version}, where tourName comes from tours[0].tour. For example, DASHBOARD_TOUR with version 1.1 produces the key tour:dashboard:v1.1.

Persistence

Key Pattern Storage Values
tour:{name}:v{version} localStorage 'completed' or 'skipped'

shouldAutoStart() checks whether the key exists — if absent, the tour has never been shown. Bumping the version parameter creates a new key, effectively resetting the tour for all users without clearing old entries.

SSR-safe: shouldAutoStart() returns false when typeof window === 'undefined'.

Tour Definitions

Tours are defined in src/config/tours/ and re-exported via src/config/tours/registry.ts:

// src/config/tours/dashboard/index.ts
const DASHBOARD_TOUR: Tour[] = [
  {
    tour: 'dashboard',
    steps: [
      {
        icon: '...',
        title: 'Video Errors',
        content: 'Monitor your video errors and track issues in real time here.',
        selector: '#video-error-widget',
        side: 'right',
        showControls: true,
        showSkip: true,
      },
      // ... more steps
    ],
  },
];

Each step targets a DOM element via CSS selector. The side property controls tooltip placement (top, bottom, left, right).

Current Tours

Tour Key Version Steps Location
Dashboard dashboard 1.1 4 src/config/tours/dashboard/index.ts

NextStepJS Integration

The tour system uses three NextStepJS components:

Component Location Purpose
NextStepProvider Route layout (overview/layout.tsx) Provides tour context to child components
NextStep Page component (overview/page.tsx) Renders the tour overlay with steps, callbacks
useNextStep Page component Hook providing startNextStep(tourName)

A custom card component (src/components/decorators/CustomCard.tsx) renders each step with:

  • Step title and content
  • Progress indicator (currentStep + 1 / totalSteps)
  • Back / Next / Skip / Finish buttons
  • Arrow pointer to the target element

Usage Pattern

The dashboard page demonstrates the standard integration:

// 1. Create manager with tour and version
const manager = useMemo(() => new TourManager(DASHBOARD_TOUR, 1.1), []);

// 2. Check on mount if tour should auto-start
useEffect(() => {
  if (manager.shouldAutoStart()) {
    setTimeout(() => showConfirmationDialog(), 2000);
  }
}, [manager]);

// 3. On confirmation → start the tour
startNextStep('dashboard');

// 4. Wire callbacks
<NextStep
  steps={manager.getTours()}
  onComplete={() => manager.markCompleted()}
  onSkip={() => manager.markSkipped()}
  cardComponent={CustomCard}
  shadowOpacity="0.5"
  shadowRgb="0, 0, 0"
/>

The 2-second delay ensures DOM elements are rendered before the tour tries to target them.

Adding a New Tour

  1. Create a tour definition file in src/config/tours/{name}/index.ts
  2. Export it from src/config/tours/registry.ts
  3. In the target page layout, wrap with <NextStepProvider>
  4. In the target page component:
    • Create a TourManager instance with the tour and a version
    • Check shouldAutoStart() on mount
    • Wire onCompletemarkCompleted() and onSkipmarkSkipped()

Resetting a Tour for All Users

Bump the version parameter:

// Before: users who completed the tour have key "tour:dashboard:v1.1"
const manager = new TourManager(DASHBOARD_TOUR, 1.1);

// After: new key "tour:dashboard:v1.2" — no one has this yet
const manager = new TourManager(DASHBOARD_TOUR, 1.2);

Old keys remain in localStorage but are inert — shouldAutoStart() only checks the current version key.

Design Decisions

  • Version-keyed persistence — changing the version resets the tour for everyone without migrations or backend coordination
  • Confirmation dialog before start — tours don't hijack the page unexpectedly; users opt in or explicitly skip
  • completed vs skipped tracking — distinguishes intentional completion from dismissal, enabling future analytics
  • No cleanup of old keys — old version keys are harmless and avoiding cleanup keeps the logic simple
  • Custom card component — decouples tour step UI from NextStepJS defaults, allowing consistent CMS styling