rails_onboarding 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1351 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/rails_onboarding/admin/chart_controller.js +25 -0
- data/app/assets/javascripts/rails_onboarding/admin/filter_controller.js +49 -0
- data/app/assets/javascripts/rails_onboarding/admin/flash_controller.js +21 -0
- data/app/assets/javascripts/rails_onboarding/admin/flow_editor_controller.js +86 -0
- data/app/assets/javascripts/rails_onboarding/application.js +231 -0
- data/app/assets/javascripts/rails_onboarding/milestone_celebration_controller.js +177 -0
- data/app/assets/javascripts/rails_onboarding/milestone_dashboard_controller.js +132 -0
- data/app/assets/javascripts/rails_onboarding/milestone_detail_controller.js +34 -0
- data/app/assets/javascripts/rails_onboarding/navigation_controller.js +277 -0
- data/app/assets/javascripts/rails_onboarding/onboarding_controller.js +326 -0
- data/app/assets/javascripts/rails_onboarding/progress_controller.js +203 -0
- data/app/assets/javascripts/rails_onboarding/progressive_disclosure_controller.js +218 -0
- data/app/assets/javascripts/rails_onboarding/tooltip_controller.js +1237 -0
- data/app/assets/javascripts/rails_onboarding/tooltip_scheduler_controller.js +511 -0
- data/app/assets/javascripts/rails_onboarding/tour_controller.js +763 -0
- data/app/assets/stylesheets/rails_onboarding/accessibility.css +536 -0
- data/app/assets/stylesheets/rails_onboarding/admin.css +816 -0
- data/app/assets/stylesheets/rails_onboarding/application.css +1145 -0
- data/app/assets/stylesheets/rails_onboarding/flash_messages.css +237 -0
- data/app/assets/stylesheets/rails_onboarding/milestones.css +545 -0
- data/app/assets/stylesheets/rails_onboarding/mobile.css +856 -0
- data/app/assets/stylesheets/rails_onboarding/progressive_disclosure.css +299 -0
- data/app/assets/stylesheets/rails_onboarding/tooltips.css +468 -0
- data/app/assets/stylesheets/rails_onboarding/tour.css +495 -0
- data/app/assets/stylesheets/rails_onboarding/utilities.css +393 -0
- data/app/controllers/concerns/rails_onboarding/admin_authorization.rb +98 -0
- data/app/controllers/concerns/rails_onboarding/rate_limitable.rb +104 -0
- data/app/controllers/rails_onboarding/ab_tests_controller.rb +257 -0
- data/app/controllers/rails_onboarding/admin/ab_tests_controller.rb +121 -0
- data/app/controllers/rails_onboarding/admin/base_controller.rb +68 -0
- data/app/controllers/rails_onboarding/admin/dashboard_controller.rb +189 -0
- data/app/controllers/rails_onboarding/admin/flows_controller.rb +175 -0
- data/app/controllers/rails_onboarding/admin/users_controller.rb +366 -0
- data/app/controllers/rails_onboarding/application_controller.rb +9 -0
- data/app/controllers/rails_onboarding/milestones_controller.rb +126 -0
- data/app/controllers/rails_onboarding/onboarding_controller.rb +409 -0
- data/app/controllers/rails_onboarding/progressive_features_controller.rb +134 -0
- data/app/controllers/rails_onboarding/templates_controller.rb +162 -0
- data/app/controllers/rails_onboarding/test_sessions_controller.rb +16 -0
- data/app/controllers/rails_onboarding/tooltips_controller.rb +92 -0
- data/app/helpers/rails_onboarding/application_helper.rb +4 -0
- data/app/helpers/rails_onboarding/i18n_helper.rb +137 -0
- data/app/helpers/rails_onboarding/personalization_helper.rb +184 -0
- data/app/helpers/rails_onboarding/templates_helper.rb +253 -0
- data/app/jobs/rails_onboarding/application_job.rb +4 -0
- data/app/mailers/rails_onboarding/application_mailer.rb +6 -0
- data/app/mailers/rails_onboarding/onboarding_mailer.rb +72 -0
- data/app/models/concerns/rails_onboarding/ab_testable.rb +149 -0
- data/app/models/concerns/rails_onboarding/onboardable.rb +649 -0
- data/app/models/concerns/rails_onboarding/personalizable.rb +211 -0
- data/app/models/concerns/rails_onboarding/progressive_disclosure.rb +276 -0
- data/app/models/rails_onboarding/analytics.rb +255 -0
- data/app/models/rails_onboarding/analytics_event.rb +168 -0
- data/app/models/rails_onboarding/application_record.rb +5 -0
- data/app/models/rails_onboarding/flow.rb +41 -0
- data/app/services/rails_onboarding/milestone_service.rb +105 -0
- data/app/views/layouts/rails_onboarding/admin.html.erb +106 -0
- data/app/views/layouts/rails_onboarding/application.html.erb +65 -0
- data/app/views/rails_onboarding/ab_tests/index.html.erb +209 -0
- data/app/views/rails_onboarding/ab_tests/results.html.erb +253 -0
- data/app/views/rails_onboarding/admin/ab_tests/_ab_test_card.html.erb +24 -0
- data/app/views/rails_onboarding/admin/ab_tests/index.html.erb +57 -0
- data/app/views/rails_onboarding/admin/ab_tests/show.html.erb +41 -0
- data/app/views/rails_onboarding/admin/dashboard/index.html.erb +224 -0
- data/app/views/rails_onboarding/admin/flows/_form.html.erb +147 -0
- data/app/views/rails_onboarding/admin/flows/edit.html.erb +7 -0
- data/app/views/rails_onboarding/admin/flows/index.html.erb +125 -0
- data/app/views/rails_onboarding/admin/flows/new.html.erb +7 -0
- data/app/views/rails_onboarding/admin/flows/preview.html.erb +40 -0
- data/app/views/rails_onboarding/admin/flows/show.html.erb +114 -0
- data/app/views/rails_onboarding/admin/users/index.html.erb +180 -0
- data/app/views/rails_onboarding/admin/users/show.html.erb +187 -0
- data/app/views/rails_onboarding/milestones/index.html.erb +64 -0
- data/app/views/rails_onboarding/milestones/show.html.erb +57 -0
- data/app/views/rails_onboarding/onboarding/_custom_footer.html.erb +2 -0
- data/app/views/rails_onboarding/onboarding/_progress_indicator.html.erb +56 -0
- data/app/views/rails_onboarding/onboarding/_step_navigation.html.erb +44 -0
- data/app/views/rails_onboarding/onboarding/step.html.erb +38 -0
- data/app/views/rails_onboarding/onboarding/welcome.html.erb +37 -0
- data/app/views/rails_onboarding/onboarding_mailer/completion_email.html.erb +109 -0
- data/app/views/rails_onboarding/onboarding_mailer/completion_email.text.erb +29 -0
- data/app/views/rails_onboarding/onboarding_mailer/reminder_email.html.erb +99 -0
- data/app/views/rails_onboarding/onboarding_mailer/reminder_email.text.erb +20 -0
- data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.html.erb +105 -0
- data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.text.erb +19 -0
- data/app/views/rails_onboarding/onboarding_mailer/welcome_email.html.erb +74 -0
- data/app/views/rails_onboarding/onboarding_mailer/welcome_email.text.erb +17 -0
- data/app/views/rails_onboarding/shared/_flash.html.erb +25 -0
- data/app/views/rails_onboarding/shared/_milestone_badge.html.erb +22 -0
- data/app/views/rails_onboarding/shared/_milestone_celebration.html.erb +44 -0
- data/app/views/rails_onboarding/shared/_onboarding_banner.html.erb +48 -0
- data/app/views/rails_onboarding/templates/index.html.erb +255 -0
- data/config/importmap.rb +33 -0
- data/config/locales/en.yml +62 -0
- data/config/locales/es.yml +62 -0
- data/config/locales/fr.yml +62 -0
- data/config/routes.rb +79 -0
- data/lib/generators/rails_onboarding/install_generator.rb +124 -0
- data/lib/generators/rails_onboarding/templates/README +120 -0
- data/lib/generators/rails_onboarding/templates/add_analytics_to_rails_onboarding.rb +33 -0
- data/lib/generators/rails_onboarding/templates/add_milestone_tracking_to_users.rb +38 -0
- data/lib/generators/rails_onboarding/templates/add_onboarding_indexes.rb +93 -0
- data/lib/generators/rails_onboarding/templates/add_onboarding_to_users.rb +51 -0
- data/lib/generators/rails_onboarding/templates/add_robustness_fields_to_users.rb.tt +20 -0
- data/lib/generators/rails_onboarding/templates/create_rails_onboarding_flows.rb +18 -0
- data/lib/generators/rails_onboarding/templates/onboarding.css +18 -0
- data/lib/generators/rails_onboarding/templates/rails_onboarding.rb +75 -0
- data/lib/generators/rails_onboarding/update_generator.rb +73 -0
- data/lib/rails_onboarding/api_mode.rb +367 -0
- data/lib/rails_onboarding/backfill.rb +130 -0
- data/lib/rails_onboarding/background_jobs.rb +412 -0
- data/lib/rails_onboarding/caching.rb +180 -0
- data/lib/rails_onboarding/cdn_support.rb +202 -0
- data/lib/rails_onboarding/configuration/ab_testing.rb +24 -0
- data/lib/rails_onboarding/configuration/analytics.rb +29 -0
- data/lib/rails_onboarding/configuration/integrations.rb +31 -0
- data/lib/rails_onboarding/configuration/milestones.rb +92 -0
- data/lib/rails_onboarding/configuration/personalization.rb +25 -0
- data/lib/rails_onboarding/configuration/progressive_disclosure.rb +41 -0
- data/lib/rails_onboarding/configuration/rate_limiting.rb +18 -0
- data/lib/rails_onboarding/configuration/steps.rb +121 -0
- data/lib/rails_onboarding/configuration/templates.rb +85 -0
- data/lib/rails_onboarding/configuration/tooltips.rb +32 -0
- data/lib/rails_onboarding/configuration.rb +133 -0
- data/lib/rails_onboarding/configuration_errors.rb +19 -0
- data/lib/rails_onboarding/configuration_validator.rb +477 -0
- data/lib/rails_onboarding/controller_helpers.rb +153 -0
- data/lib/rails_onboarding/current.rb +13 -0
- data/lib/rails_onboarding/deprecation.rb +64 -0
- data/lib/rails_onboarding/devise_integration.rb +122 -0
- data/lib/rails_onboarding/engine.rb +245 -0
- data/lib/rails_onboarding/error_recovery.rb +173 -0
- data/lib/rails_onboarding/lazy_loading.rb +155 -0
- data/lib/rails_onboarding/multi_tenant.rb +306 -0
- data/lib/rails_onboarding/requirements_validator.rb +315 -0
- data/lib/rails_onboarding/responsive_helper.rb +218 -0
- data/lib/rails_onboarding/session_manager.rb +187 -0
- data/lib/rails_onboarding/skip_logic.rb +159 -0
- data/lib/rails_onboarding/turbo_compatibility.rb +229 -0
- data/lib/rails_onboarding/version.rb +3 -0
- data/lib/rails_onboarding.rb +44 -0
- data/lib/tasks/rails_onboarding_analytics.rake +136 -0
- data/lib/tasks/rails_onboarding_tasks.rake +162 -0
- metadata +303 -0
data/README.md
ADDED
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
# Rails Onboarding
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/rails_onboarding)
|
|
4
|
+
[](https://github.com/bunnahabhain/rails_onboarding/actions)
|
|
5
|
+
|
|
6
|
+
A flexible, customizable onboarding engine for Rails applications. Create engaging multi-step onboarding flows with progress tracking, tooltips, milestones, analytics, and more.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🎯 **Multi-Step Onboarding Flows** - Guide users through customizable onboarding steps
|
|
11
|
+
- 📊 **Progress Tracking** - Visual progress indicators and completion tracking
|
|
12
|
+
- 💬 **Smart Tooltips** - Context-aware feature tooltips with progressive disclosure
|
|
13
|
+
- 🏆 **Milestone System** - Achievement tracking with points and celebrations
|
|
14
|
+
- 📈 **Analytics & Metrics** - Comprehensive tracking of completion rates, drop-off points, and user engagement
|
|
15
|
+
- 🎨 **Interactive Tours** - Guided walkthroughs with spotlight effects
|
|
16
|
+
- 🧪 **A/B Testing** - Test different onboarding flows and measure effectiveness
|
|
17
|
+
- 👥 **Personalization** - Adapt onboarding based on user type or role
|
|
18
|
+
- 🌍 **Internationalization** - Built-in support for multiple languages
|
|
19
|
+
- 📱 **Mobile Responsive** - Fully responsive design for all devices
|
|
20
|
+
- ⚡ **Performance Optimized** - Caching, database optimization, lazy loading, and CDN support
|
|
21
|
+
- 🔌 **Easy Integration** - Works seamlessly with Devise, Turbo, and Stimulus
|
|
22
|
+
- 🎭 **Multi-Tenant Support** - Different onboarding flows per organization
|
|
23
|
+
- 📦 **Pre-built Templates** - 5 ready-to-use onboarding flows for common use cases
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Quick Start](#quick-start)
|
|
29
|
+
- [Configuration](#configuration)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Advanced Features](#advanced-features)
|
|
32
|
+
- [Admin Dashboard](#admin-dashboard)
|
|
33
|
+
- [API Reference](#api-reference)
|
|
34
|
+
- [Testing](#testing)
|
|
35
|
+
- [Contributing](#contributing)
|
|
36
|
+
- [License](#license)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Add this line to your application's Gemfile:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
gem "rails_onboarding"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then execute:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run the installation generator:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
rails generate rails_onboarding:install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Prerequisite:** A `User` model must already exist at `app/models/user.rb`. The generator checks for it and stops with an error if it's missing. Create one first (for example `rails generate model User email:string`) if you don't have it yet.
|
|
59
|
+
|
|
60
|
+
This will:
|
|
61
|
+
- Create an initializer at `config/initializers/rails_onboarding.rb`
|
|
62
|
+
- Generate six migrations: onboarding fields on your `User` model, the analytics event tables, the onboarding flows table, milestone tracking fields, performance indexes, and robustness/error-recovery fields
|
|
63
|
+
- Mount the engine in your routes at `/onboarding`
|
|
64
|
+
- Add a customizable stylesheet at `app/assets/stylesheets/rails_onboarding_custom.css`
|
|
65
|
+
|
|
66
|
+
Run the migrations:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bin/rails db:migrate
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The `User` migration adds `onboarding_completed`, `onboarding_completed_at`, `onboarding_current_step`, `onboarding_skipped`, and `feature_tooltips_shown` (stored as `jsonb` on PostgreSQL, `json` on MySQL/MariaDB, and serialized `text` elsewhere), along with the indexes needed to query onboarding state efficiently. On PostgreSQL these indexes are built concurrently so the migration won't lock a large, live `users` table.
|
|
73
|
+
|
|
74
|
+
### Installing into an app that already has users
|
|
75
|
+
|
|
76
|
+
The migration leaves every existing user with empty onboarding columns, so the
|
|
77
|
+
admin reports them all as "Not Started" and — depending on your
|
|
78
|
+
`onboarding_required_for` setting — they may be pushed into a flow they don't
|
|
79
|
+
need. Backfill them once, after migrating:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# See what would change first
|
|
83
|
+
bin/rails rails_onboarding:backfill_existing_users DRY_RUN=true
|
|
84
|
+
|
|
85
|
+
# Apply it
|
|
86
|
+
bin/rails rails_onboarding:backfill_existing_users
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Variable | Default | Purpose |
|
|
90
|
+
|----------|---------|---------|
|
|
91
|
+
| `BEFORE` | none | Only backfill users created before this date, e.g. `BEFORE=2026-07-01`. Use it when the gem has been live for a while and you only want to grandfather in the pre-install cohort. Users with a `NULL` `created_at` are always included. |
|
|
92
|
+
| `DRY_RUN` | `false` | Report the count without writing. |
|
|
93
|
+
| `BATCH_SIZE` | `1000` | Rows per `UPDATE`. |
|
|
94
|
+
|
|
95
|
+
Only users who have never engaged with onboarding are touched — not completed,
|
|
96
|
+
not skipped, and on no current step — so anyone mid-flow is left alone and the
|
|
97
|
+
task is safe to re-run on a live app. `onboarding_completed_at` is taken from
|
|
98
|
+
each user's own `created_at` (falling back to now when that is `NULL`), which
|
|
99
|
+
keeps your completion-over-time charts from spiking on backfill day. No
|
|
100
|
+
analytics events are recorded: these users never actually went through
|
|
101
|
+
onboarding, and inventing events would corrupt your funnel metrics.
|
|
102
|
+
|
|
103
|
+
It's also available from the console, where `pending_scope` lets you inspect
|
|
104
|
+
exactly who is about to be affected:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
RailsOnboarding::Backfill.pending_scope(created_before: "2026-07-01").pluck(:id, :email)
|
|
108
|
+
RailsOnboarding::Backfill.mark_existing_users_onboarded(created_before: "2026-07-01")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Requirements
|
|
112
|
+
|
|
113
|
+
This gem requires:
|
|
114
|
+
1. **Rails 8.0+** (enforced by the gemspec)
|
|
115
|
+
2. **Ruby 3.4.9+** (enforced by the gemspec)
|
|
116
|
+
3. An `ApplicationController` class
|
|
117
|
+
4. A `current_user` method available in your controllers
|
|
118
|
+
5. Authentication system (Devise, custom, etc.)
|
|
119
|
+
|
|
120
|
+
### Version Compatibility Matrix
|
|
121
|
+
|
|
122
|
+
| Rails Version | Ruby Version | rails_onboarding | Status | Notes |
|
|
123
|
+
|--------------|--------------|------------------|--------|-------|
|
|
124
|
+
| 8.0.x | 3.4.9+ | 0.1.0+ | ✅ Supported | Minimum required by the gemspec |
|
|
125
|
+
| < 8.0 | - | - | ❌ Not Supported | Rails 8.0+ required |
|
|
126
|
+
| any | < 3.4.9 | - | ❌ Not Supported | Ruby 3.4.9+ required |
|
|
127
|
+
|
|
128
|
+
**Legend:**
|
|
129
|
+
- ✅ **Supported**: Meets the gemspec's version constraints
|
|
130
|
+
- ❌ **Not Supported**: Will not install (blocked by the gemspec's version constraints)
|
|
131
|
+
|
|
132
|
+
**Feature/Toolchain Compatibility (Rails 8.0+):**
|
|
133
|
+
|
|
134
|
+
| Feature | Supported | Notes |
|
|
135
|
+
|---------|-----------|-------|
|
|
136
|
+
| Core Onboarding | ✅ | |
|
|
137
|
+
| Turbo Integration | ✅ | Via turbo-rails |
|
|
138
|
+
| Stimulus Controllers | ✅ | Via stimulus-rails |
|
|
139
|
+
| Importmap | ✅ | Native support |
|
|
140
|
+
| Asset Pipeline (Sprockets) | ✅ | |
|
|
141
|
+
| Propshaft | ✅ | |
|
|
142
|
+
| ESBuild/Webpack | ✅ | Via jsbundling-rails |
|
|
143
|
+
| PostgreSQL | ✅ | Recommended for JSONB |
|
|
144
|
+
| MySQL/MariaDB | ✅ | JSON field support |
|
|
145
|
+
| SQLite | ✅ | Development/testing only |
|
|
146
|
+
|
|
147
|
+
### Dependencies
|
|
148
|
+
|
|
149
|
+
The gem has both required and optional dependencies:
|
|
150
|
+
|
|
151
|
+
#### Required Dependencies
|
|
152
|
+
|
|
153
|
+
These dependencies are automatically installed with the gem:
|
|
154
|
+
|
|
155
|
+
| Gem | Version | Purpose |
|
|
156
|
+
|-----|---------|---------|
|
|
157
|
+
| rails | >= 8.0.0 | Core Rails framework |
|
|
158
|
+
| csv | (latest) | Admin CSV exports (no longer a Ruby default gem as of Ruby 3.4) |
|
|
159
|
+
|
|
160
|
+
#### Optional Dependencies
|
|
161
|
+
|
|
162
|
+
These enhance functionality but are not required:
|
|
163
|
+
|
|
164
|
+
| Gem | Version | Purpose | When Needed |
|
|
165
|
+
|-----|---------|---------|-------------|
|
|
166
|
+
| stimulus-rails | >= 1.0.0 | Interactive JavaScript controllers | For Stimulus-based interactivity (tooltips, tours, navigation) |
|
|
167
|
+
| turbo-rails | >= 1.0.0 | Hotwire/Turbo integration | For seamless page transitions and real-time updates |
|
|
168
|
+
| importmap-rails | >= 1.0.0 | JavaScript module loading | If using importmaps for asset management |
|
|
169
|
+
| jsbundling-rails | >= 1.0.0 | JavaScript bundling | If using ESBuild/Webpack for assets |
|
|
170
|
+
| cssbundling-rails | >= 1.0.0 | CSS bundling | If using Tailwind/PostCSS/Sass |
|
|
171
|
+
| propshaft | >= 0.6.0 | Asset pipeline | Alternative to Sprockets for Rails 7+ |
|
|
172
|
+
| sprockets-rails | >= 3.4.0 | Asset pipeline | Traditional asset pipeline support |
|
|
173
|
+
| pg | >= 1.1 | PostgreSQL adapter | For JSONB field support (recommended) |
|
|
174
|
+
| mysql2 | >= 0.5 | MySQL adapter | For JSON field support |
|
|
175
|
+
| sqlite3 | >= 1.4 | SQLite adapter | Development/testing |
|
|
176
|
+
| sidekiq | >= 6.0 | Background jobs | For async email sending |
|
|
177
|
+
| resque | >= 2.0 | Background jobs | Alternative job processor |
|
|
178
|
+
| delayed_job | >= 4.1 | Background jobs | Alternative job processor |
|
|
179
|
+
| redis | >= 4.0 | Caching | For production caching with Redis |
|
|
180
|
+
|
|
181
|
+
#### Installation Recommendations
|
|
182
|
+
|
|
183
|
+
**Minimal Installation (Core Features Only):**
|
|
184
|
+
```ruby
|
|
185
|
+
# Gemfile
|
|
186
|
+
gem "rails_onboarding"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Recommended Installation (Full Features):**
|
|
190
|
+
```ruby
|
|
191
|
+
# Gemfile
|
|
192
|
+
gem "rails_onboarding"
|
|
193
|
+
gem "stimulus-rails"
|
|
194
|
+
gem "turbo-rails"
|
|
195
|
+
gem "importmap-rails" # or jsbundling-rails
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Full-Featured Installation (All Advanced Features):**
|
|
199
|
+
```ruby
|
|
200
|
+
# Gemfile
|
|
201
|
+
gem "rails_onboarding"
|
|
202
|
+
gem "stimulus-rails"
|
|
203
|
+
gem "turbo-rails"
|
|
204
|
+
gem "importmap-rails"
|
|
205
|
+
gem "sidekiq" # for background jobs
|
|
206
|
+
gem "redis" # for caching
|
|
207
|
+
gem "pg" # for PostgreSQL/JSONB support
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Feature Requirements
|
|
211
|
+
|
|
212
|
+
Different features require different optional dependencies:
|
|
213
|
+
|
|
214
|
+
| Feature | Required Dependencies | Optional Dependencies |
|
|
215
|
+
|---------|----------------------|----------------------|
|
|
216
|
+
| Core Onboarding | rails | - |
|
|
217
|
+
| Interactive Tooltips | rails | stimulus-rails |
|
|
218
|
+
| Guided Tours | rails | stimulus-rails |
|
|
219
|
+
| Turbo Navigation | rails | turbo-rails, stimulus-rails |
|
|
220
|
+
| Background Emails | rails | sidekiq/resque/delayed_job, actionmailer |
|
|
221
|
+
| Production Caching | rails | redis, actionpack |
|
|
222
|
+
| JSONB Tooltips/Milestones | rails | pg (PostgreSQL) |
|
|
223
|
+
| Analytics Tracking | rails | - |
|
|
224
|
+
| A/B Testing | rails | - |
|
|
225
|
+
| Multi-Tenant | rails | - |
|
|
226
|
+
|
|
227
|
+
#### Checking for Optional Dependencies
|
|
228
|
+
|
|
229
|
+
The gem automatically detects available dependencies:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# Check if Stimulus is available
|
|
233
|
+
RailsOnboarding.stimulus_available?
|
|
234
|
+
|
|
235
|
+
# Check if Turbo is available
|
|
236
|
+
RailsOnboarding.turbo_available?
|
|
237
|
+
|
|
238
|
+
# Check if background jobs are available
|
|
239
|
+
RailsOnboarding.background_jobs_available?
|
|
240
|
+
|
|
241
|
+
# The gem gracefully degrades if optional dependencies are missing
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Quick Start
|
|
245
|
+
|
|
246
|
+
### 1. Include the Onboardable Concern
|
|
247
|
+
|
|
248
|
+
Add the concern to your User model:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
# app/models/user.rb
|
|
252
|
+
class User < ApplicationRecord
|
|
253
|
+
include RailsOnboarding::Onboardable
|
|
254
|
+
|
|
255
|
+
# Your existing code...
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 2. Configure Your Onboarding Flow
|
|
260
|
+
|
|
261
|
+
Edit the generated initializer:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# config/initializers/rails_onboarding.rb
|
|
265
|
+
RailsOnboarding.configure do |config|
|
|
266
|
+
config.user_class_name = 'User'
|
|
267
|
+
config.redirect_after_completion = :dashboard_path
|
|
268
|
+
config.redirect_after_skip = :root_path
|
|
269
|
+
|
|
270
|
+
config.steps = [
|
|
271
|
+
{ name: :welcome, title: 'Welcome!', icon: '👋', skippable: true },
|
|
272
|
+
{ name: :profile, title: 'Setup Profile', icon: '👤', skippable: false },
|
|
273
|
+
{ name: :preferences, title: 'Preferences', icon: '⚙️', skippable: true },
|
|
274
|
+
{ name: :explore, title: 'Explore Features', icon: '🔍', skippable: true }
|
|
275
|
+
]
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 3. Protect Your Controllers
|
|
280
|
+
|
|
281
|
+
`RailsOnboarding::ControllerHelpers` is included into `ActionController::Base`
|
|
282
|
+
automatically by the engine, giving every controller a `needs_onboarding?`
|
|
283
|
+
predicate (it already accounts for XHR/JSON requests, the onboarding page
|
|
284
|
+
itself, and per-action skips - see below). The gem doesn't redirect for you;
|
|
285
|
+
wire up a `before_action` in your own `ApplicationController`:
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
# app/controllers/application_controller.rb
|
|
289
|
+
class ApplicationController < ActionController::Base
|
|
290
|
+
before_action :redirect_to_onboarding_if_needed
|
|
291
|
+
|
|
292
|
+
private
|
|
293
|
+
|
|
294
|
+
def redirect_to_onboarding_if_needed
|
|
295
|
+
redirect_to onboarding_path if needs_onboarding?
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Skip onboarding for specific actions:
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
class ProfilesController < ApplicationController
|
|
304
|
+
skip_onboarding_check only: [:edit, :update] # or except: [...]
|
|
305
|
+
|
|
306
|
+
def edit
|
|
307
|
+
# Users can access this during onboarding
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 4. Customize Step Views
|
|
313
|
+
|
|
314
|
+
Create custom views for your onboarding steps:
|
|
315
|
+
|
|
316
|
+
```erb
|
|
317
|
+
<!-- app/views/rails_onboarding/onboarding/welcome.html.erb -->
|
|
318
|
+
<div class="onboarding-step">
|
|
319
|
+
<h1>Welcome to <%= Rails.application.class.name %>!</h1>
|
|
320
|
+
<p>Let's get you started with a quick tour.</p>
|
|
321
|
+
|
|
322
|
+
<%= link_to "Get Started", next_onboarding_path,
|
|
323
|
+
method: :post,
|
|
324
|
+
class: "btn btn-primary" %>
|
|
325
|
+
</div>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Configuration
|
|
329
|
+
|
|
330
|
+
### Basic Configuration
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
RailsOnboarding.configure do |config|
|
|
334
|
+
# User model configuration
|
|
335
|
+
config.user_class_name = 'User'
|
|
336
|
+
|
|
337
|
+
# Redirect paths
|
|
338
|
+
config.redirect_after_completion = :dashboard_path
|
|
339
|
+
config.redirect_after_skip = :root_path
|
|
340
|
+
|
|
341
|
+
# Feature flags
|
|
342
|
+
config.enable_tooltips = true
|
|
343
|
+
config.enable_milestones = true
|
|
344
|
+
config.enable_analytics = true
|
|
345
|
+
|
|
346
|
+
# Onboarding requirements
|
|
347
|
+
config.onboarding_required_for = :new_users # or :all_users, :none
|
|
348
|
+
|
|
349
|
+
# Define your onboarding steps
|
|
350
|
+
config.steps = [
|
|
351
|
+
{ name: :welcome, title: 'Welcome', icon: '🎉', skippable: true },
|
|
352
|
+
{ name: :profile, title: 'Complete Profile', icon: '👤', skippable: false },
|
|
353
|
+
{ name: :first_action, title: 'Take Action', icon: '🚀', skippable: false },
|
|
354
|
+
{ name: :explore, title: 'Explore', icon: '🔍', skippable: true }
|
|
355
|
+
]
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Step Configuration Options
|
|
360
|
+
|
|
361
|
+
Each step supports these options:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
{
|
|
365
|
+
name: :step_name, # Required: Unique identifier
|
|
366
|
+
title: 'Step Title', # Required: Display title
|
|
367
|
+
icon: '🎯', # Optional: Emoji or icon class
|
|
368
|
+
skippable: true, # Optional: Can user skip this step?
|
|
369
|
+
description: 'Details...', # Optional: Additional context
|
|
370
|
+
estimated_time: '2 min', # Optional: Time estimate
|
|
371
|
+
required_fields: [:name], # Optional: Required user fields
|
|
372
|
+
condition: ->(user) { ... },# Optional: Show step conditionally
|
|
373
|
+
path: :new_profile_path, # Optional: Host-app page that owns this step
|
|
374
|
+
# (Symbol/String route helper, or a zero-arg
|
|
375
|
+
# Proc evaluated in the controller context)
|
|
376
|
+
complete_if: ->(user) { # Optional: Predicate checked on each visit to
|
|
377
|
+
user.profile.present? # /onboarding - when true, the step is
|
|
378
|
+
} # completed and the user advances
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Reusing Existing Controllers and Views (`path:` steps)
|
|
383
|
+
|
|
384
|
+
By default each step is rendered by the gem from
|
|
385
|
+
`app/views/rails_onboarding/onboarding/<step_name>.html.erb`. That's right
|
|
386
|
+
for pure-content steps (a welcome screen, a feature tour), but wrong for
|
|
387
|
+
steps your app already has a page for - duplicating your profile form into
|
|
388
|
+
an onboarding template means two copies to keep in sync.
|
|
389
|
+
|
|
390
|
+
Give those steps a `path:` instead. Visiting `/onboarding` then redirects to
|
|
391
|
+
your real page, and your existing controller and view do all the work - the
|
|
392
|
+
gem only tracks progress:
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
config.steps = [
|
|
396
|
+
{ name: :welcome, title: 'Welcome', skippable: true }, # gem-rendered
|
|
397
|
+
{ name: :profile, title: 'Set up your profile', icon: '👤',
|
|
398
|
+
path: :new_profile_path,
|
|
399
|
+
complete_if: ->(user) { user.profile.present? } },
|
|
400
|
+
{ name: :first_post, title: 'Create your first post',
|
|
401
|
+
path: -> { main_app.new_post_path(from: 'onboarding') },
|
|
402
|
+
complete_if: ->(user) { user.posts.exists? } }
|
|
403
|
+
]
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
A `path:` step is completed in one of two ways:
|
|
407
|
+
|
|
408
|
+
1. **Declaratively** via `complete_if:` - whenever the user lands back on
|
|
409
|
+
`/onboarding`, every satisfied step is skipped automatically. Your
|
|
410
|
+
controllers never have to know onboarding exists.
|
|
411
|
+
2. **Explicitly** via `advance_onboarding!` in the host controller - useful
|
|
412
|
+
when completion isn't derivable from persisted state:
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
class ProfilesController < ApplicationController
|
|
416
|
+
def create
|
|
417
|
+
@profile = current_user.build_profile(profile_params)
|
|
418
|
+
if @profile.save
|
|
419
|
+
return redirect_to onboarding_path if advance_onboarding!(:profile)
|
|
420
|
+
redirect_to @profile
|
|
421
|
+
else
|
|
422
|
+
render :new, status: :unprocessable_entity
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`advance_onboarding!` is a no-op unless the named step is the user's
|
|
429
|
+
*current* onboarding step, so the same action behaves normally when the
|
|
430
|
+
user edits their profile outside onboarding.
|
|
431
|
+
|
|
432
|
+
A `path:` step with neither `complete_if:` nor `skippable: true` can only
|
|
433
|
+
advance through an explicit `advance_onboarding!` call - the validator logs
|
|
434
|
+
a warning for such steps so a missing call doesn't silently trap users.
|
|
435
|
+
|
|
436
|
+
To keep the guided feel while users are on your own pages, render the
|
|
437
|
+
gem's progress banner from your layout. It shows current step, progress,
|
|
438
|
+
a "Continue setup" link, and a skip button (on skippable steps), and renders
|
|
439
|
+
nothing once onboarding is done, skipped, or not required:
|
|
440
|
+
|
|
441
|
+
```erb
|
|
442
|
+
<body>
|
|
443
|
+
<%= render "rails_onboarding/shared/onboarding_banner" %>
|
|
444
|
+
<%= yield %>
|
|
445
|
+
</body>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
If you guard pages with `redirect_to onboarding_path if needs_onboarding?`,
|
|
449
|
+
that guard automatically stays off the current step's own page - the gem
|
|
450
|
+
recognizes it and lets the request through, so the redirect loop you'd
|
|
451
|
+
expect from `/onboarding` bouncing to the page and back can't happen.
|
|
452
|
+
|
|
453
|
+
### Milestone Configuration
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
RailsOnboarding.configure do |config|
|
|
457
|
+
config.enable_milestones = true
|
|
458
|
+
|
|
459
|
+
config.milestones = [
|
|
460
|
+
{
|
|
461
|
+
key: :profile_complete,
|
|
462
|
+
title: 'Profile Master',
|
|
463
|
+
description: 'Completed your profile',
|
|
464
|
+
points: 100,
|
|
465
|
+
icon: '👤',
|
|
466
|
+
trigger: :profile_completed
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
key: :first_post,
|
|
470
|
+
title: 'Content Creator',
|
|
471
|
+
description: 'Created your first post',
|
|
472
|
+
points: 50,
|
|
473
|
+
icon: '📝',
|
|
474
|
+
trigger: :post_created
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
end
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Each milestone is identified by `key:`, not `id:` — `milestone_by_key`,
|
|
481
|
+
`milestone_achieved?` and the achievement records all look it up by that
|
|
482
|
+
field. A milestone without `key:` fails configuration validation at boot with
|
|
483
|
+
`InvalidMilestoneError`, so a typo here surfaces immediately rather than
|
|
484
|
+
producing achievements nothing can find. `trigger:` is required as well;
|
|
485
|
+
`title`, `description`, `points` and `icon` are what the milestone views
|
|
486
|
+
display.
|
|
487
|
+
|
|
488
|
+
### Analytics Configuration
|
|
489
|
+
|
|
490
|
+
```ruby
|
|
491
|
+
RailsOnboarding.configure do |config|
|
|
492
|
+
config.enable_analytics = true
|
|
493
|
+
|
|
494
|
+
# How long to keep analytics rows (days)
|
|
495
|
+
config.analytics_retention_days = 90
|
|
496
|
+
|
|
497
|
+
# Inactivity after which a visit counts as a new session
|
|
498
|
+
config.analytics_session_timeout_minutes = 30
|
|
499
|
+
end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Onboarding events (`onboarding_started`, `step_viewed`, `step_completed`,
|
|
503
|
+
`onboarding_completed`, `onboarding_skipped`, tooltip views and dismissals) are
|
|
504
|
+
recorded automatically while `enable_analytics` is on; there is no per-event
|
|
505
|
+
opt-in list.
|
|
506
|
+
|
|
507
|
+
## Usage
|
|
508
|
+
|
|
509
|
+
### Checking Onboarding Status
|
|
510
|
+
|
|
511
|
+
```ruby
|
|
512
|
+
# In your controllers or views
|
|
513
|
+
if current_user.needs_onboarding?
|
|
514
|
+
redirect_to rails_onboarding.onboarding_path
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Check completion
|
|
518
|
+
current_user.onboarding_completed? # => true/false
|
|
519
|
+
current_user.onboarding_in_progress? # => true/false
|
|
520
|
+
|
|
521
|
+
# Get progress
|
|
522
|
+
current_user.onboarding_progress # => 75 (percentage)
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Managing Onboarding State
|
|
526
|
+
|
|
527
|
+
```ruby
|
|
528
|
+
# Advance to next step
|
|
529
|
+
current_user.advance_step!
|
|
530
|
+
|
|
531
|
+
# Go back to previous step
|
|
532
|
+
current_user.go_back!
|
|
533
|
+
|
|
534
|
+
# Complete onboarding
|
|
535
|
+
current_user.complete_onboarding!
|
|
536
|
+
|
|
537
|
+
# Skip onboarding
|
|
538
|
+
current_user.skip_onboarding!
|
|
539
|
+
|
|
540
|
+
# Restart onboarding
|
|
541
|
+
current_user.restart_onboarding!
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Working with Tooltips
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
# Check if tooltip has been shown
|
|
548
|
+
current_user.tooltip_shown?('feature_dashboard') # => false
|
|
549
|
+
|
|
550
|
+
# Mark tooltip as shown
|
|
551
|
+
current_user.mark_tooltip_shown!('feature_dashboard')
|
|
552
|
+
|
|
553
|
+
# Reset all tooltips
|
|
554
|
+
current_user.reset_tooltips!
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
There's no `tooltip_tag` helper - render the Stimulus controller directly,
|
|
558
|
+
gated by `show_feature_tooltip?`, with the copy in a `content` target (the
|
|
559
|
+
controller can't read your Ruby config, so the text has to be in the DOM)
|
|
560
|
+
and the dismiss URL pointed at the engine's real route:
|
|
561
|
+
|
|
562
|
+
```erb
|
|
563
|
+
<% if current_user.show_feature_tooltip?("feature_reports") %>
|
|
564
|
+
<div data-controller="tooltip"
|
|
565
|
+
data-tooltip-feature-value="feature_reports"
|
|
566
|
+
data-tooltip-position-value="bottom"
|
|
567
|
+
data-tooltip-delay-value="1000"
|
|
568
|
+
data-tooltip-dismiss-url-value="<%= rails_onboarding.dismiss_tooltip_path(tooltip_id: "feature_reports") %>">
|
|
569
|
+
<button data-tooltip-target="trigger" class="btn">Reports</button>
|
|
570
|
+
<div data-tooltip-target="content" hidden>
|
|
571
|
+
Click here to view your reports
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
<% end %>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Register `config.feature_tooltips["feature_reports"]` in your initializer
|
|
578
|
+
first (see [Configuration](#configuration) below) - `show_feature_tooltip?`
|
|
579
|
+
returns false for anything not configured there, even if the DOM above is
|
|
580
|
+
present.
|
|
581
|
+
|
|
582
|
+
### Working with Milestones
|
|
583
|
+
|
|
584
|
+
```ruby
|
|
585
|
+
# Check milestone achievement
|
|
586
|
+
current_user.milestone_achieved?(:profile_complete) # => false
|
|
587
|
+
|
|
588
|
+
# Record an achievement. Points come from the milestone's configuration -
|
|
589
|
+
# they are not passed in. Returns false if milestones are disabled, the key
|
|
590
|
+
# is unknown, or it was already achieved.
|
|
591
|
+
current_user.achieve_milestone!(:profile_complete) # => true
|
|
592
|
+
|
|
593
|
+
# Get user's points
|
|
594
|
+
current_user.total_milestone_points # => 150
|
|
595
|
+
|
|
596
|
+
# List achieved milestone keys
|
|
597
|
+
current_user.achieved_milestones # => ['first_login', 'profile_complete']
|
|
598
|
+
|
|
599
|
+
# Turn those keys into renderable hashes
|
|
600
|
+
current_user.achieved_milestones.filter_map { |key|
|
|
601
|
+
RailsOnboarding.configuration.milestone_by_key(key)
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
#### Displaying milestones
|
|
606
|
+
|
|
607
|
+
The engine ships a milestones dashboard at `rails_onboarding.milestones_path`,
|
|
608
|
+
and two partials you render yourself when you want achievements to appear
|
|
609
|
+
inside your own pages. Both take a milestone hash in the shape
|
|
610
|
+
`config.milestones` uses — `key`, `title`, `description`, `points`, `icon`.
|
|
611
|
+
|
|
612
|
+
**A badge**, for listing achievements in a profile or sidebar. `achieved:`
|
|
613
|
+
decides whether it renders unlocked or with a lock overlay:
|
|
614
|
+
|
|
615
|
+
```erb
|
|
616
|
+
<% RailsOnboarding.configuration.milestones.each do |milestone| %>
|
|
617
|
+
<%= render "rails_onboarding/shared/milestone_badge",
|
|
618
|
+
milestone: milestone,
|
|
619
|
+
achieved: current_user.milestone_achieved?(milestone[:key]) %>
|
|
620
|
+
<% end %>
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**A celebration**, a modal overlay that shows itself as soon as it connects,
|
|
624
|
+
fires confetti, and auto-dismisses after eight seconds. Render it only when
|
|
625
|
+
you have something to celebrate — it is not self-guarding the way the
|
|
626
|
+
onboarding banner is:
|
|
627
|
+
|
|
628
|
+
```erb
|
|
629
|
+
<% if (milestone = current_user.recent_milestones(limit: 1).first) %>
|
|
630
|
+
<%= render "rails_onboarding/shared/milestone_celebration",
|
|
631
|
+
milestone: milestone %>
|
|
632
|
+
<% end %>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
Both partials need `rails_onboarding/milestones.css` loaded, and the
|
|
636
|
+
celebration additionally needs its Stimulus controller registered — without
|
|
637
|
+
it the overlay stays hidden, since it is rendered with `is-hidden` and the
|
|
638
|
+
controller is what reveals it. See the
|
|
639
|
+
[Asset Loading Guide](docs/ASSET_LOADING_GUIDE.md) for both.
|
|
640
|
+
|
|
641
|
+
Useful sources for the milestone hashes:
|
|
642
|
+
|
|
643
|
+
| Call | Returns |
|
|
644
|
+
|---|---|
|
|
645
|
+
| `RailsOnboarding.configuration.milestones` | every configured milestone |
|
|
646
|
+
| `RailsOnboarding.configuration.milestone_by_key(key)` | one milestone, or `nil` |
|
|
647
|
+
| `current_user.milestones_available` | configured milestones not yet achieved |
|
|
648
|
+
| `current_user.recent_milestones(limit: 5)` | most recently achieved, newest last |
|
|
649
|
+
| `current_user.achieved_milestones` | achieved **keys**, not hashes |
|
|
650
|
+
| `current_user.total_milestone_points` | integer |
|
|
651
|
+
|
|
652
|
+
Note that `achieved_milestones` returns keys rather than hashes; pass each
|
|
653
|
+
through `milestone_by_key` to get something the partials can render.
|
|
654
|
+
|
|
655
|
+
### Flash Messages
|
|
656
|
+
|
|
657
|
+
The engine reports some conditions — a step that can't be skipped, a "back"
|
|
658
|
+
from the first step — by replacing a `flash-messages` element over Turbo.
|
|
659
|
+
That element only exists if you render the gem's flash partial, so without it
|
|
660
|
+
those messages are silently dropped. Render it once in your layout:
|
|
661
|
+
|
|
662
|
+
```erb
|
|
663
|
+
<body>
|
|
664
|
+
<%= render "rails_onboarding/shared/flash" %>
|
|
665
|
+
<%= yield %>
|
|
666
|
+
</body>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Rendered with no locals it displays `flash[:notice]` and `flash[:alert]`,
|
|
670
|
+
because `notice` and `alert` are the standard Rails view helpers. You can
|
|
671
|
+
also pass messages explicitly, which is what the engine's own Turbo responses
|
|
672
|
+
do:
|
|
673
|
+
|
|
674
|
+
```erb
|
|
675
|
+
<%= render "rails_onboarding/shared/flash", alert: "This step cannot be skipped." %>
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
| Local | Style | Notes |
|
|
679
|
+
|---|---|---|
|
|
680
|
+
| `notice:` | success (green, ✓) | falls back to `flash[:notice]` |
|
|
681
|
+
| `alert:` | warning (amber, ⚠) | falls back to `flash[:alert]` |
|
|
682
|
+
| `error:` | error (red, ✕) | explicit only — there is no `error` view helper |
|
|
683
|
+
|
|
684
|
+
Each message renders with `role="alert"` and its own dismiss button, which
|
|
685
|
+
needs no JavaScript from you. Styling comes from
|
|
686
|
+
`rails_onboarding/flash_messages.css`.
|
|
687
|
+
|
|
688
|
+
Two things worth knowing. The partial's root element carries
|
|
689
|
+
`id="flash-messages"`, and that is what the engine's Turbo Stream responses
|
|
690
|
+
replace — so keep the id if you copy the partial into your own app, or those
|
|
691
|
+
updates will stop landing. And unlike the onboarding banner, this partial is
|
|
692
|
+
not self-guarding: rendered with no flash set and no locals it emits an empty
|
|
693
|
+
container, which is harmless but not nothing, so place it where an empty
|
|
694
|
+
`div` won't disturb your layout.
|
|
695
|
+
|
|
696
|
+
### Analytics and Reporting
|
|
697
|
+
|
|
698
|
+
```ruby
|
|
699
|
+
# Get completion rate
|
|
700
|
+
RailsOnboarding::Analytics.completion_rate(30.days.ago..Time.current)
|
|
701
|
+
# => 0.75
|
|
702
|
+
|
|
703
|
+
# Get average completion time
|
|
704
|
+
RailsOnboarding::Analytics.average_completion_time
|
|
705
|
+
# => 320.5 (seconds)
|
|
706
|
+
|
|
707
|
+
# Get step funnel
|
|
708
|
+
RailsOnboarding::Analytics.step_funnel
|
|
709
|
+
# => { welcome: { started: 100, completed: 95 }, profile: { started: 95, completed: 80 }, ... }
|
|
710
|
+
|
|
711
|
+
# Get drop-off points
|
|
712
|
+
RailsOnboarding::Analytics.drop_off_points
|
|
713
|
+
# => [{ step: 'profile', drop_off_rate: 0.15 }, ...]
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Run analytics reports:
|
|
717
|
+
|
|
718
|
+
```bash
|
|
719
|
+
# Daily summary
|
|
720
|
+
rails rails_onboarding:analytics:daily_summary
|
|
721
|
+
|
|
722
|
+
# Weekly summary
|
|
723
|
+
rails rails_onboarding:analytics:weekly_summary
|
|
724
|
+
|
|
725
|
+
# Export data
|
|
726
|
+
rails rails_onboarding:analytics:export[30]
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## Advanced Features
|
|
730
|
+
|
|
731
|
+
### A/B Testing
|
|
732
|
+
|
|
733
|
+
Test different onboarding flows:
|
|
734
|
+
|
|
735
|
+
```ruby
|
|
736
|
+
# In your User model
|
|
737
|
+
include RailsOnboarding::AbTestable
|
|
738
|
+
|
|
739
|
+
# Assign variant
|
|
740
|
+
current_user.assign_ab_variant('flow_test', 'variant_b')
|
|
741
|
+
|
|
742
|
+
# Track conversion
|
|
743
|
+
current_user.track_ab_conversion('flow_test')
|
|
744
|
+
|
|
745
|
+
# Get results
|
|
746
|
+
RailsOnboarding::AbTest.results('flow_test')
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Personalization
|
|
750
|
+
|
|
751
|
+
Adapt onboarding based on user type:
|
|
752
|
+
|
|
753
|
+
```ruby
|
|
754
|
+
# In your initializer
|
|
755
|
+
RailsOnboarding.configure do |config|
|
|
756
|
+
config.personalization_enabled = true
|
|
757
|
+
|
|
758
|
+
# Method called on the user to determine their type (default: :user_type)
|
|
759
|
+
config.user_type_method = :user_type
|
|
760
|
+
|
|
761
|
+
# Each flow is a plain array of steps, keyed by user type
|
|
762
|
+
config.personalized_flows = {
|
|
763
|
+
developer: [
|
|
764
|
+
{ name: :setup_api, title: 'Setup API Keys' },
|
|
765
|
+
{ name: :first_integration, title: 'First Integration' }
|
|
766
|
+
],
|
|
767
|
+
marketer: [
|
|
768
|
+
{ name: :create_campaign, title: 'Create Campaign' },
|
|
769
|
+
{ name: :add_tracking, title: 'Add Tracking' }
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
end
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
Include `RailsOnboarding::Personalizable` in your User model, and the matching
|
|
776
|
+
flow is applied when onboarding starts:
|
|
777
|
+
|
|
778
|
+
```ruby
|
|
779
|
+
current_user.update(user_type: 'developer')
|
|
780
|
+
current_user.personalized_steps # => the developer flow
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Progressive Disclosure
|
|
784
|
+
|
|
785
|
+
Reveal features over time:
|
|
786
|
+
|
|
787
|
+
```ruby
|
|
788
|
+
# In your controller
|
|
789
|
+
class FeaturesController < ApplicationController
|
|
790
|
+
include RailsOnboarding::ProgressiveDisclosure
|
|
791
|
+
|
|
792
|
+
def advanced_reports
|
|
793
|
+
feature = ProgressiveFeature.find_by(feature_key: 'advanced_reports')
|
|
794
|
+
|
|
795
|
+
if feature_unlocked_for?(current_user, feature)
|
|
796
|
+
# Show feature
|
|
797
|
+
else
|
|
798
|
+
# Show teaser or locked state
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### Multi-Tenant Support
|
|
805
|
+
|
|
806
|
+
Different onboarding per organization:
|
|
807
|
+
|
|
808
|
+
```ruby
|
|
809
|
+
# Configure per-organization
|
|
810
|
+
RailsOnboarding::MultiTenant.configure_for_organization(org.id) do |config|
|
|
811
|
+
config.steps = org.custom_onboarding_steps
|
|
812
|
+
config.enable_tooltips = org.tooltips_enabled?
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
# Use in your app
|
|
816
|
+
config = RailsOnboarding::MultiTenant.configuration_for(current_user.organization_id)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Interactive Tours
|
|
820
|
+
|
|
821
|
+
Create guided tours with spotlight effects:
|
|
822
|
+
|
|
823
|
+
```erb
|
|
824
|
+
<div data-controller="tour"
|
|
825
|
+
data-tour-steps-value='<%= tour_steps.to_json %>'>
|
|
826
|
+
|
|
827
|
+
<button data-action="tour#start">Start Tour</button>
|
|
828
|
+
|
|
829
|
+
<div data-tour-target="dashboard">Dashboard</div>
|
|
830
|
+
<div data-tour-target="reports">Reports</div>
|
|
831
|
+
</div>
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Background Jobs
|
|
835
|
+
|
|
836
|
+
Queue onboarding emails and notifications:
|
|
837
|
+
|
|
838
|
+
```ruby
|
|
839
|
+
# In your config
|
|
840
|
+
RailsOnboarding.configure do |config|
|
|
841
|
+
config.background_jobs_enabled = true
|
|
842
|
+
config.background_jobs_queue = :default
|
|
843
|
+
end
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
`BackgroundJobs` is a concern. Include it where you want to queue work, and
|
|
847
|
+
configure the adapter per class if the ActiveJob default doesn't suit:
|
|
848
|
+
|
|
849
|
+
```ruby
|
|
850
|
+
class OnboardingController < ApplicationController
|
|
851
|
+
include RailsOnboarding::BackgroundJobs
|
|
852
|
+
|
|
853
|
+
configure_background_jobs(adapter: :sidekiq, queue: :onboarding)
|
|
854
|
+
|
|
855
|
+
def complete
|
|
856
|
+
queue_onboarding_welcome_email(current_user)
|
|
857
|
+
queue_milestone_achievement(current_user, :first_login)
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### Skip Logic
|
|
863
|
+
|
|
864
|
+
Conditionally skip steps based on user data:
|
|
865
|
+
|
|
866
|
+
Skip conditions live on the step itself, under `skip_if:`. It accepts a proc, a
|
|
867
|
+
predicate method name, or a hash:
|
|
868
|
+
|
|
869
|
+
```ruby
|
|
870
|
+
RailsOnboarding.configure do |config|
|
|
871
|
+
config.steps = [
|
|
872
|
+
{ name: :welcome, title: 'Welcome' },
|
|
873
|
+
{ name: :profile, title: 'Setup Profile',
|
|
874
|
+
skip_if: ->(user) { user.profile_complete? } },
|
|
875
|
+
{ name: :payment, title: 'Add Payment',
|
|
876
|
+
skip_if: :free_plan? },
|
|
877
|
+
{ name: :team, title: 'Invite Team',
|
|
878
|
+
skip_if: { account_type: 'individual' } }
|
|
879
|
+
]
|
|
880
|
+
end
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
The hash form matches an attribute by default, and also understands
|
|
884
|
+
`has_attribute`, `missing_attribute`, `attribute_equals`,
|
|
885
|
+
`attribute_not_equals`, `has_role` and `custom`. Combine several with
|
|
886
|
+
`operator: :all` (the default), `:any`, or `:none`:
|
|
887
|
+
|
|
888
|
+
```ruby
|
|
889
|
+
skip_if: {
|
|
890
|
+
operator: :any,
|
|
891
|
+
has_role: :admin,
|
|
892
|
+
missing_attribute: :company_name
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
A condition that raises is logged and treated as false, so a broken predicate
|
|
897
|
+
never blocks the flow.
|
|
898
|
+
|
|
899
|
+
### Error Recovery
|
|
900
|
+
|
|
901
|
+
Handle failures gracefully:
|
|
902
|
+
|
|
903
|
+
```ruby
|
|
904
|
+
# Retry failed steps
|
|
905
|
+
current_user.retry_step!('profile')
|
|
906
|
+
|
|
907
|
+
# Track errors
|
|
908
|
+
RailsOnboarding::ErrorRecovery.log_error(user, step, error)
|
|
909
|
+
|
|
910
|
+
# Get error count
|
|
911
|
+
current_user.step_error_count('profile')
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
## Admin Dashboard
|
|
915
|
+
|
|
916
|
+
The engine ships a full admin dashboard - a stats overview, per-user onboarding management (view progress, reset, complete, or restart onboarding), a flow editor, and A/B test management. It's mounted automatically wherever you mounted the engine, at `/admin` under that path (`/onboarding/admin` if you used the default mount point from the installer).
|
|
917
|
+
|
|
918
|
+
It ships gated behind an authorization check you need to wire up - by default nothing has admin access.
|
|
919
|
+
|
|
920
|
+
### 1. Add an `admin?` method to your `User` model
|
|
921
|
+
|
|
922
|
+
```ruby
|
|
923
|
+
class User < ApplicationRecord
|
|
924
|
+
def admin?
|
|
925
|
+
role == "admin" # or however you track it
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
With this in place and a `current_user` method available on your `ApplicationController` (e.g. from Devise), the default check - `current_user.present? && current_user.admin?` - is all you need.
|
|
931
|
+
|
|
932
|
+
### 2. Or override the authentication method for custom logic
|
|
933
|
+
|
|
934
|
+
If you need something more specific than `current_user.admin?` (a different gate, a different method name for the current user, etc.), define this instead and it takes priority over the default check entirely:
|
|
935
|
+
|
|
936
|
+
```ruby
|
|
937
|
+
class ApplicationController < ActionController::Base
|
|
938
|
+
def authenticate_rails_onboarding_admin!
|
|
939
|
+
redirect_to root_path, alert: "Not authorized" unless current_user&.admin?
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Leaving both `admin?` and `authenticate_rails_onboarding_admin!` undefined isn't a silent hole - visiting the dashboard raises a `NotImplementedError` that redirects with a message telling you to define one of them.
|
|
945
|
+
|
|
946
|
+
No extra asset tags are required for the dashboard itself: its layout (`app/views/layouts/rails_onboarding/admin.html.erb`) is self-contained with its own `<head>` and stylesheet/JS tags, separate from your app's layout.
|
|
947
|
+
|
|
948
|
+
### 3. Customizing the user search (encrypted email columns)
|
|
949
|
+
|
|
950
|
+
The User Management search box matches on email and ID with plain SQL. If your
|
|
951
|
+
`User` model encrypts email, that SQL is comparing against ciphertext, so the
|
|
952
|
+
admin adapts automatically: deterministic encryption falls back to exact
|
|
953
|
+
address matching, and non-deterministic encryption skips email entirely. ID
|
|
954
|
+
search always works.
|
|
955
|
+
|
|
956
|
+
Partial matching over an encrypted column can't be done in SQL at all. Only
|
|
957
|
+
your app knows what it's willing to trade for it, so supply the strategy
|
|
958
|
+
yourself:
|
|
959
|
+
|
|
960
|
+
```ruby
|
|
961
|
+
# config/initializers/rails_onboarding.rb
|
|
962
|
+
config.admin_user_search = lambda do |scope, term|
|
|
963
|
+
needle = term.to_s.downcase
|
|
964
|
+
ids = scope.select(:id, :email).filter_map do |user|
|
|
965
|
+
user.id if user.email.to_s.downcase.include?(needle)
|
|
966
|
+
end
|
|
967
|
+
scope.where(id: ids)
|
|
968
|
+
end
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
That example decrypts in Ruby, which leaks nothing and needs no schema change,
|
|
972
|
+
but is O(n) per search — fine for hundreds or low thousands of users, not for
|
|
973
|
+
a large table. The alternative is a searchable index of your own (a trigram or
|
|
974
|
+
prefix blind index), which scales but leaks substantially more about the
|
|
975
|
+
addresses than deterministic encryption alone.
|
|
976
|
+
|
|
977
|
+
The lambda receives the scope already narrowed by the status and step filters,
|
|
978
|
+
plus the raw search term, and **must return a relation** — returning an array
|
|
979
|
+
silently drops sorting and pagination.
|
|
980
|
+
|
|
981
|
+
## Pre-built Templates
|
|
982
|
+
|
|
983
|
+
Use ready-made onboarding flows:
|
|
984
|
+
|
|
985
|
+
```ruby
|
|
986
|
+
# Apply a template
|
|
987
|
+
RailsOnboarding::Templates.apply('saas')
|
|
988
|
+
|
|
989
|
+
# Available templates:
|
|
990
|
+
# - 'saas' - SaaS application onboarding
|
|
991
|
+
# - 'ecommerce' - E-commerce store setup
|
|
992
|
+
# - 'marketplace' - Marketplace seller onboarding
|
|
993
|
+
# - 'community' - Community platform onboarding
|
|
994
|
+
# - 'education' - Educational platform onboarding
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
## API Reference
|
|
998
|
+
|
|
999
|
+
### User Model Methods (Onboardable Concern)
|
|
1000
|
+
|
|
1001
|
+
#### Status Methods
|
|
1002
|
+
- `needs_onboarding?` - Returns true if user needs to complete onboarding
|
|
1003
|
+
- `onboarding_completed?` - Returns true if onboarding is complete
|
|
1004
|
+
- `onboarding_in_progress?` - Returns true if onboarding is in progress
|
|
1005
|
+
- `onboarding_skipped?` - Returns true if user skipped onboarding
|
|
1006
|
+
|
|
1007
|
+
#### Navigation Methods
|
|
1008
|
+
- `current_step_index` - Returns index of current step
|
|
1009
|
+
- `next_step` - Returns next step name
|
|
1010
|
+
- `previous_step` - Returns previous step name
|
|
1011
|
+
- `can_go_back?` - Returns true if can navigate backwards
|
|
1012
|
+
- `last_step?` - Returns true if on last step
|
|
1013
|
+
|
|
1014
|
+
#### Action Methods
|
|
1015
|
+
- `advance_step!` - Move to next step
|
|
1016
|
+
- `go_back!` - Move to previous step
|
|
1017
|
+
- `complete_onboarding!` - Mark onboarding as complete
|
|
1018
|
+
- `skip_onboarding!` - Skip onboarding
|
|
1019
|
+
- `restart_onboarding!` - Restart from beginning
|
|
1020
|
+
|
|
1021
|
+
#### Progress Methods
|
|
1022
|
+
- `onboarding_progress` - Returns completion percentage (0-100)
|
|
1023
|
+
|
|
1024
|
+
#### Tooltip Methods
|
|
1025
|
+
- `tooltip_shown?(tooltip_id)` - Check if tooltip was shown
|
|
1026
|
+
- `mark_tooltip_shown!(tooltip_id)` - Mark tooltip as shown
|
|
1027
|
+
- `reset_tooltips!` - Reset all tooltips
|
|
1028
|
+
|
|
1029
|
+
#### Milestone Methods
|
|
1030
|
+
- `milestone_achieved?(key)` - Check achievement status
|
|
1031
|
+
- `achieve_milestone!(key, session_id: nil)` - Record achievement; points are read from the milestone's configuration
|
|
1032
|
+
- `total_milestone_points` - Get total points
|
|
1033
|
+
- `achieved_milestones` - Get achieved milestone **keys** (not hashes)
|
|
1034
|
+
- `milestones_available` - Configured milestones not yet achieved, as hashes
|
|
1035
|
+
- `recent_milestones(limit: 5)` - Most recently achieved, as hashes, newest last
|
|
1036
|
+
|
|
1037
|
+
### Controller Helpers
|
|
1038
|
+
|
|
1039
|
+
- `require_onboarding` - Before action to enforce onboarding
|
|
1040
|
+
- `skip_onboarding_requirement` - Skip requirement for specific actions
|
|
1041
|
+
- `user_needs_onboarding?` - Check if current user needs onboarding
|
|
1042
|
+
- `onboarding_path` - Get path to onboarding flow
|
|
1043
|
+
|
|
1044
|
+
### Configuration Class
|
|
1045
|
+
|
|
1046
|
+
- `RailsOnboarding.configure { |config| ... }` - Configure the gem
|
|
1047
|
+
- `RailsOnboarding.configuration` - Access current configuration
|
|
1048
|
+
- `RailsOnboarding.reset_configuration!` - Reset to defaults
|
|
1049
|
+
|
|
1050
|
+
### Analytics Class
|
|
1051
|
+
|
|
1052
|
+
- `Analytics.completion_rate(date_range)` - Get completion rate
|
|
1053
|
+
- `Analytics.average_completion_time` - Get average time
|
|
1054
|
+
- `Analytics.step_funnel` - Get step-by-step funnel data
|
|
1055
|
+
- `Analytics.drop_off_points` - Identify where users drop off
|
|
1056
|
+
- `Analytics.tooltip_engagement` - Get tooltip metrics
|
|
1057
|
+
|
|
1058
|
+
## Testing
|
|
1059
|
+
|
|
1060
|
+
The gem includes comprehensive test coverage. Run tests with:
|
|
1061
|
+
|
|
1062
|
+
```bash
|
|
1063
|
+
# Run all tests
|
|
1064
|
+
bundle exec rails test
|
|
1065
|
+
|
|
1066
|
+
# Run specific test file
|
|
1067
|
+
bundle exec rails test test/integration/navigation_test.rb
|
|
1068
|
+
|
|
1069
|
+
# Run with coverage
|
|
1070
|
+
COVERAGE=true bundle exec rails test
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
### Testing in Your Application
|
|
1074
|
+
|
|
1075
|
+
Add helpers to your test suite:
|
|
1076
|
+
|
|
1077
|
+
```ruby
|
|
1078
|
+
# test/test_helper.rb
|
|
1079
|
+
require 'rails_onboarding/test_helpers'
|
|
1080
|
+
|
|
1081
|
+
class ActiveSupport::TestCase
|
|
1082
|
+
include RailsOnboarding::TestHelpers
|
|
1083
|
+
end
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
Use in tests:
|
|
1087
|
+
|
|
1088
|
+
```ruby
|
|
1089
|
+
class MyFeatureTest < ActionDispatch::IntegrationTest
|
|
1090
|
+
test "requires onboarding" do
|
|
1091
|
+
user = create_user_needing_onboarding
|
|
1092
|
+
|
|
1093
|
+
sign_in user
|
|
1094
|
+
get dashboard_path
|
|
1095
|
+
|
|
1096
|
+
assert_redirected_to onboarding_path
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
test "allows access after onboarding" do
|
|
1100
|
+
user = create_user_with_completed_onboarding
|
|
1101
|
+
|
|
1102
|
+
sign_in user
|
|
1103
|
+
get dashboard_path
|
|
1104
|
+
|
|
1105
|
+
assert_response :success
|
|
1106
|
+
end
|
|
1107
|
+
end
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## Internationalization
|
|
1111
|
+
|
|
1112
|
+
The gem supports multiple languages:
|
|
1113
|
+
|
|
1114
|
+
```yaml
|
|
1115
|
+
# config/locales/rails_onboarding.en.yml
|
|
1116
|
+
en:
|
|
1117
|
+
rails_onboarding:
|
|
1118
|
+
welcome:
|
|
1119
|
+
title: "Welcome!"
|
|
1120
|
+
description: "Let's get started"
|
|
1121
|
+
buttons:
|
|
1122
|
+
next: "Next"
|
|
1123
|
+
previous: "Back"
|
|
1124
|
+
skip: "Skip for now"
|
|
1125
|
+
complete: "Finish"
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
Set user locale:
|
|
1129
|
+
|
|
1130
|
+
```ruby
|
|
1131
|
+
# Before onboarding
|
|
1132
|
+
current_user.update(locale: 'es')
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
## Troubleshooting
|
|
1136
|
+
|
|
1137
|
+
### Onboarding not triggering
|
|
1138
|
+
|
|
1139
|
+
Ensure you have:
|
|
1140
|
+
1. Included the concern in your User model
|
|
1141
|
+
2. Run the migrations
|
|
1142
|
+
3. Added a `before_action` that redirects when `needs_onboarding?` is true (see [Protect Your Controllers](#3-protect-your-controllers) - the gem doesn't add this callback for you)
|
|
1143
|
+
4. Configured at least one step
|
|
1144
|
+
|
|
1145
|
+
### Assets not loading
|
|
1146
|
+
|
|
1147
|
+
The engine's CSS and JS aren't included in your layout automatically.
|
|
1148
|
+
|
|
1149
|
+
**On Sprockets**, `rails_onboarding/application.css` bundles all of the
|
|
1150
|
+
gem's stylesheets itself via internal `require` directives, so one tag is
|
|
1151
|
+
enough:
|
|
1152
|
+
|
|
1153
|
+
```erb
|
|
1154
|
+
<%= stylesheet_link_tag "rails_onboarding/application" %>
|
|
1155
|
+
<%= javascript_include_tag "rails_onboarding/application", defer: true %>
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
You can alternatively link them via the manifest:
|
|
1159
|
+
|
|
1160
|
+
```js
|
|
1161
|
+
// app/assets/config/manifest.js
|
|
1162
|
+
//= link rails_onboarding/application.css
|
|
1163
|
+
//= link rails_onboarding/application.js
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
**On Propshaft** (Rails 8's default, including with cssbundling-rails),
|
|
1167
|
+
there's no bundling directive processor - `application.css`'s internal
|
|
1168
|
+
`require` directives are inert comments, so it only contains its own base
|
|
1169
|
+
styles/tokens. Link every stylesheet individually or you'll silently be
|
|
1170
|
+
missing tooltips, tours, milestones, mobile responsiveness, and more:
|
|
1171
|
+
|
|
1172
|
+
```erb
|
|
1173
|
+
<%= stylesheet_link_tag "rails_onboarding/application" %>
|
|
1174
|
+
<%= stylesheet_link_tag "rails_onboarding/tooltips" %>
|
|
1175
|
+
<%= stylesheet_link_tag "rails_onboarding/utilities" %>
|
|
1176
|
+
<%= stylesheet_link_tag "rails_onboarding/accessibility" %>
|
|
1177
|
+
<%= stylesheet_link_tag "rails_onboarding/milestones" %>
|
|
1178
|
+
<%= stylesheet_link_tag "rails_onboarding/tour" %>
|
|
1179
|
+
<%= stylesheet_link_tag "rails_onboarding/flash_messages" %>
|
|
1180
|
+
<%= stylesheet_link_tag "rails_onboarding/progressive_disclosure" %>
|
|
1181
|
+
<%= stylesheet_link_tag "rails_onboarding/admin" %>
|
|
1182
|
+
<%= stylesheet_link_tag "rails_onboarding/mobile" %>
|
|
1183
|
+
<%= javascript_include_tag "rails_onboarding/application", defer: true %>
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
Keep `application` first (it defines the `--onboarding-*` custom properties
|
|
1187
|
+
the rest use) and `mobile` last (its media-query overrides need to win
|
|
1188
|
+
cascade ties). Skip `admin` if you don't use the admin dashboard. This is
|
|
1189
|
+
unrelated to cssbundling-rails - these tags bypass your JS/CSS build
|
|
1190
|
+
pipeline and are served by Propshaft directly.
|
|
1191
|
+
|
|
1192
|
+
If you're on Importmap, the engine's `config/importmap.rb` pins are drawn
|
|
1193
|
+
into your app's importmap automatically - you don't need to pin anything
|
|
1194
|
+
yourself. You do still need to import and register the Stimulus controllers
|
|
1195
|
+
you use (see [Asset Loading Guide](docs/ASSET_LOADING_GUIDE.md)), since
|
|
1196
|
+
pinning a module only makes it loadable, not registered with Stimulus.
|
|
1197
|
+
|
|
1198
|
+
### Gem styles bleeding into my app's pages
|
|
1199
|
+
|
|
1200
|
+
They shouldn't, and as of 0.4.0 they don't. Every rule in
|
|
1201
|
+
the gem's stylesheets is confined to markup the engine owns, scoped with
|
|
1202
|
+
`:where(.onboarding-container)` for engine pages or
|
|
1203
|
+
`:where(.onboarding-banner)` for the banner rendered on your own pages. It
|
|
1204
|
+
is safe to bundle the gem's CSS globally.
|
|
1205
|
+
|
|
1206
|
+
If you are on an older version and see symptoms like every link on your
|
|
1207
|
+
homepage suddenly underlined, tables and fieldsets restyled, or buttons
|
|
1208
|
+
forced to a 44px minimum, that's the pre-fix `accessibility.css` — it
|
|
1209
|
+
carried unscoped `a`, `table`, `fieldset`, `button` and `[role="dialog"]`
|
|
1210
|
+
selectors, and under Sprockets `*= require rails_onboarding/application`
|
|
1211
|
+
pulls in every gem stylesheet via `require_tree`. Upgrade, or drop the
|
|
1212
|
+
`require` from your host `application.css` and link the individual
|
|
1213
|
+
stylesheets you need.
|
|
1214
|
+
|
|
1215
|
+
Two utility classes are namespaced rather than scoped, because they are
|
|
1216
|
+
meant to be used outside the container: `.onboarding-sr-only` (screen-reader
|
|
1217
|
+
text) and `.onboarding-skip-link` (a skip link has to be the first focusable
|
|
1218
|
+
element in the document). If you used the older `.sr-only` / `.skip-link`
|
|
1219
|
+
names in your own markup, rename them.
|
|
1220
|
+
|
|
1221
|
+
The gem does define `--onboarding-*` custom properties on `:root`, which is
|
|
1222
|
+
intentional — that's the supported way to retheme it from your own
|
|
1223
|
+
stylesheet:
|
|
1224
|
+
|
|
1225
|
+
```css
|
|
1226
|
+
:root {
|
|
1227
|
+
--onboarding-primary: #0f766e;
|
|
1228
|
+
--onboarding-primary-hover: #115e59;
|
|
1229
|
+
}
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
### Do I need to load accessibility.css?
|
|
1233
|
+
|
|
1234
|
+
Yes — load it like any other gem stylesheet. It isn't gated on a user
|
|
1235
|
+
preference and there's no toggle to switch it on for people who need it.
|
|
1236
|
+
Most of it is unconditional baseline: focus indicators, 44px touch targets,
|
|
1237
|
+
screen-reader text, disabled-state contrast. Focus rings use `:focus-visible`,
|
|
1238
|
+
so browsers already paint them only for keyboard and assistive-technology
|
|
1239
|
+
users, not on mouse clicks. The parts that *are* preference-dependent —
|
|
1240
|
+
`prefers-contrast: high`, `prefers-reduced-motion: reduce`,
|
|
1241
|
+
`prefers-color-scheme: dark` — are gated by the browser off the user's OS
|
|
1242
|
+
setting, with nothing for your app to detect or configure.
|
|
1243
|
+
|
|
1244
|
+
### Stimulus controllers not working
|
|
1245
|
+
|
|
1246
|
+
Ensure Stimulus is installed and configured:
|
|
1247
|
+
|
|
1248
|
+
```bash
|
|
1249
|
+
bin/rails stimulus:manifest:update
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
## Upgrading
|
|
1253
|
+
|
|
1254
|
+
See [MIGRATION_GUIDE.md](docs/MIGRATION_GUIDE.md) for version upgrade instructions.
|
|
1255
|
+
|
|
1256
|
+
## Performance Considerations
|
|
1257
|
+
|
|
1258
|
+
### Caching
|
|
1259
|
+
|
|
1260
|
+
Enable caching for better performance:
|
|
1261
|
+
|
|
1262
|
+
Caching is opt-in at the call site rather than through configuration - include
|
|
1263
|
+
`RailsOnboarding::Caching` and use the cached readers, each taking its own
|
|
1264
|
+
`ttl:` in seconds:
|
|
1265
|
+
|
|
1266
|
+
```ruby
|
|
1267
|
+
# config/environments/production.rb
|
|
1268
|
+
config.cache_store = :redis_cache_store
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
```ruby
|
|
1272
|
+
class User < ApplicationRecord
|
|
1273
|
+
include RailsOnboarding::Caching
|
|
1274
|
+
end
|
|
1275
|
+
|
|
1276
|
+
User.cached_steps(ttl: 1.hour) # configuration lookups
|
|
1277
|
+
current_user.cached_onboarding_progress # per-user, defaults to 5 minutes
|
|
1278
|
+
|
|
1279
|
+
# Call after changing configuration
|
|
1280
|
+
User.clear_config_cache
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
### Database Indexes
|
|
1284
|
+
|
|
1285
|
+
Add indexes for frequently queried fields:
|
|
1286
|
+
|
|
1287
|
+
```ruby
|
|
1288
|
+
add_index :users, :onboarding_completed
|
|
1289
|
+
add_index :users, :onboarding_current_step
|
|
1290
|
+
add_index :analytics_events, [:user_id, :event_type]
|
|
1291
|
+
add_index :analytics_events, :created_at
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
## Security
|
|
1295
|
+
|
|
1296
|
+
The gem follows Rails security best practices:
|
|
1297
|
+
- CSRF protection enabled
|
|
1298
|
+
- SQL injection prevention
|
|
1299
|
+
- XSS protection with content sanitization
|
|
1300
|
+
- Secure session handling
|
|
1301
|
+
|
|
1302
|
+
## Browser Support
|
|
1303
|
+
|
|
1304
|
+
- Chrome/Edge (last 2 versions)
|
|
1305
|
+
- Firefox (last 2 versions)
|
|
1306
|
+
- Safari (last 2 versions)
|
|
1307
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
1308
|
+
|
|
1309
|
+
## Contributing
|
|
1310
|
+
|
|
1311
|
+
Contributions are welcome. In short:
|
|
1312
|
+
|
|
1313
|
+
1. Fork the repository and branch from `master`
|
|
1314
|
+
2. Write a test for your change
|
|
1315
|
+
3. Check it with `bin/rails test` and `bin/rubocop`
|
|
1316
|
+
4. Add a changelog entry if the change is user-visible
|
|
1317
|
+
5. Open a pull request
|
|
1318
|
+
|
|
1319
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, how the test suite is laid
|
|
1320
|
+
out, and the conventions this project follows.
|
|
1321
|
+
|
|
1322
|
+
## Documentation
|
|
1323
|
+
|
|
1324
|
+
- [Advanced Features Guide](docs/ADVANCED_FEATURES.md)
|
|
1325
|
+
- [Milestone System Guide](docs/MILESTONES_GUIDE.md)
|
|
1326
|
+
- [Analytics Guide](docs/ANALYTICS_GUIDE.md)
|
|
1327
|
+
- [Responsive Design Guide](docs/RESPONSIVE_DESIGN.md)
|
|
1328
|
+
- [Performance & Scalability Guide](docs/PERFORMANCE_GUIDE.md)
|
|
1329
|
+
- [API Documentation](docs/API_DOCUMENTATION.md)
|
|
1330
|
+
- [Migration Guide](docs/MIGRATION_GUIDE.md)
|
|
1331
|
+
- [ESBuild Setup Guide](docs/ESBUILD_SETUP.md)
|
|
1332
|
+
- [Configuration Validation Guide](docs/CONFIGURATION_VALIDATION.md)
|
|
1333
|
+
|
|
1334
|
+
## Credits
|
|
1335
|
+
|
|
1336
|
+
Created and maintained by [David Lewis](https://github.com/bunnahabhain).
|
|
1337
|
+
|
|
1338
|
+
## License
|
|
1339
|
+
|
|
1340
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
1341
|
+
|
|
1342
|
+
## Support
|
|
1343
|
+
|
|
1344
|
+
- 📫 Email: david@davidsfolly.com
|
|
1345
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/bunnahabhain/rails_onboarding/issues)
|
|
1346
|
+
- 💬 Discussions: [GitHub Discussions](https://github.com/bunnahabhain/rails_onboarding/discussions)
|
|
1347
|
+
- 📖 Wiki: [GitHub Wiki](https://github.com/bunnahabhain/rails_onboarding/wiki)
|
|
1348
|
+
|
|
1349
|
+
## Changelog
|
|
1350
|
+
|
|
1351
|
+
See [CHANGELOG.md](docs/CHANGELOG.md) for a list of changes.
|