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.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1351 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/rails_onboarding/admin/chart_controller.js +25 -0
  6. data/app/assets/javascripts/rails_onboarding/admin/filter_controller.js +49 -0
  7. data/app/assets/javascripts/rails_onboarding/admin/flash_controller.js +21 -0
  8. data/app/assets/javascripts/rails_onboarding/admin/flow_editor_controller.js +86 -0
  9. data/app/assets/javascripts/rails_onboarding/application.js +231 -0
  10. data/app/assets/javascripts/rails_onboarding/milestone_celebration_controller.js +177 -0
  11. data/app/assets/javascripts/rails_onboarding/milestone_dashboard_controller.js +132 -0
  12. data/app/assets/javascripts/rails_onboarding/milestone_detail_controller.js +34 -0
  13. data/app/assets/javascripts/rails_onboarding/navigation_controller.js +277 -0
  14. data/app/assets/javascripts/rails_onboarding/onboarding_controller.js +326 -0
  15. data/app/assets/javascripts/rails_onboarding/progress_controller.js +203 -0
  16. data/app/assets/javascripts/rails_onboarding/progressive_disclosure_controller.js +218 -0
  17. data/app/assets/javascripts/rails_onboarding/tooltip_controller.js +1237 -0
  18. data/app/assets/javascripts/rails_onboarding/tooltip_scheduler_controller.js +511 -0
  19. data/app/assets/javascripts/rails_onboarding/tour_controller.js +763 -0
  20. data/app/assets/stylesheets/rails_onboarding/accessibility.css +536 -0
  21. data/app/assets/stylesheets/rails_onboarding/admin.css +816 -0
  22. data/app/assets/stylesheets/rails_onboarding/application.css +1145 -0
  23. data/app/assets/stylesheets/rails_onboarding/flash_messages.css +237 -0
  24. data/app/assets/stylesheets/rails_onboarding/milestones.css +545 -0
  25. data/app/assets/stylesheets/rails_onboarding/mobile.css +856 -0
  26. data/app/assets/stylesheets/rails_onboarding/progressive_disclosure.css +299 -0
  27. data/app/assets/stylesheets/rails_onboarding/tooltips.css +468 -0
  28. data/app/assets/stylesheets/rails_onboarding/tour.css +495 -0
  29. data/app/assets/stylesheets/rails_onboarding/utilities.css +393 -0
  30. data/app/controllers/concerns/rails_onboarding/admin_authorization.rb +98 -0
  31. data/app/controllers/concerns/rails_onboarding/rate_limitable.rb +104 -0
  32. data/app/controllers/rails_onboarding/ab_tests_controller.rb +257 -0
  33. data/app/controllers/rails_onboarding/admin/ab_tests_controller.rb +121 -0
  34. data/app/controllers/rails_onboarding/admin/base_controller.rb +68 -0
  35. data/app/controllers/rails_onboarding/admin/dashboard_controller.rb +189 -0
  36. data/app/controllers/rails_onboarding/admin/flows_controller.rb +175 -0
  37. data/app/controllers/rails_onboarding/admin/users_controller.rb +366 -0
  38. data/app/controllers/rails_onboarding/application_controller.rb +9 -0
  39. data/app/controllers/rails_onboarding/milestones_controller.rb +126 -0
  40. data/app/controllers/rails_onboarding/onboarding_controller.rb +409 -0
  41. data/app/controllers/rails_onboarding/progressive_features_controller.rb +134 -0
  42. data/app/controllers/rails_onboarding/templates_controller.rb +162 -0
  43. data/app/controllers/rails_onboarding/test_sessions_controller.rb +16 -0
  44. data/app/controllers/rails_onboarding/tooltips_controller.rb +92 -0
  45. data/app/helpers/rails_onboarding/application_helper.rb +4 -0
  46. data/app/helpers/rails_onboarding/i18n_helper.rb +137 -0
  47. data/app/helpers/rails_onboarding/personalization_helper.rb +184 -0
  48. data/app/helpers/rails_onboarding/templates_helper.rb +253 -0
  49. data/app/jobs/rails_onboarding/application_job.rb +4 -0
  50. data/app/mailers/rails_onboarding/application_mailer.rb +6 -0
  51. data/app/mailers/rails_onboarding/onboarding_mailer.rb +72 -0
  52. data/app/models/concerns/rails_onboarding/ab_testable.rb +149 -0
  53. data/app/models/concerns/rails_onboarding/onboardable.rb +649 -0
  54. data/app/models/concerns/rails_onboarding/personalizable.rb +211 -0
  55. data/app/models/concerns/rails_onboarding/progressive_disclosure.rb +276 -0
  56. data/app/models/rails_onboarding/analytics.rb +255 -0
  57. data/app/models/rails_onboarding/analytics_event.rb +168 -0
  58. data/app/models/rails_onboarding/application_record.rb +5 -0
  59. data/app/models/rails_onboarding/flow.rb +41 -0
  60. data/app/services/rails_onboarding/milestone_service.rb +105 -0
  61. data/app/views/layouts/rails_onboarding/admin.html.erb +106 -0
  62. data/app/views/layouts/rails_onboarding/application.html.erb +65 -0
  63. data/app/views/rails_onboarding/ab_tests/index.html.erb +209 -0
  64. data/app/views/rails_onboarding/ab_tests/results.html.erb +253 -0
  65. data/app/views/rails_onboarding/admin/ab_tests/_ab_test_card.html.erb +24 -0
  66. data/app/views/rails_onboarding/admin/ab_tests/index.html.erb +57 -0
  67. data/app/views/rails_onboarding/admin/ab_tests/show.html.erb +41 -0
  68. data/app/views/rails_onboarding/admin/dashboard/index.html.erb +224 -0
  69. data/app/views/rails_onboarding/admin/flows/_form.html.erb +147 -0
  70. data/app/views/rails_onboarding/admin/flows/edit.html.erb +7 -0
  71. data/app/views/rails_onboarding/admin/flows/index.html.erb +125 -0
  72. data/app/views/rails_onboarding/admin/flows/new.html.erb +7 -0
  73. data/app/views/rails_onboarding/admin/flows/preview.html.erb +40 -0
  74. data/app/views/rails_onboarding/admin/flows/show.html.erb +114 -0
  75. data/app/views/rails_onboarding/admin/users/index.html.erb +180 -0
  76. data/app/views/rails_onboarding/admin/users/show.html.erb +187 -0
  77. data/app/views/rails_onboarding/milestones/index.html.erb +64 -0
  78. data/app/views/rails_onboarding/milestones/show.html.erb +57 -0
  79. data/app/views/rails_onboarding/onboarding/_custom_footer.html.erb +2 -0
  80. data/app/views/rails_onboarding/onboarding/_progress_indicator.html.erb +56 -0
  81. data/app/views/rails_onboarding/onboarding/_step_navigation.html.erb +44 -0
  82. data/app/views/rails_onboarding/onboarding/step.html.erb +38 -0
  83. data/app/views/rails_onboarding/onboarding/welcome.html.erb +37 -0
  84. data/app/views/rails_onboarding/onboarding_mailer/completion_email.html.erb +109 -0
  85. data/app/views/rails_onboarding/onboarding_mailer/completion_email.text.erb +29 -0
  86. data/app/views/rails_onboarding/onboarding_mailer/reminder_email.html.erb +99 -0
  87. data/app/views/rails_onboarding/onboarding_mailer/reminder_email.text.erb +20 -0
  88. data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.html.erb +105 -0
  89. data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.text.erb +19 -0
  90. data/app/views/rails_onboarding/onboarding_mailer/welcome_email.html.erb +74 -0
  91. data/app/views/rails_onboarding/onboarding_mailer/welcome_email.text.erb +17 -0
  92. data/app/views/rails_onboarding/shared/_flash.html.erb +25 -0
  93. data/app/views/rails_onboarding/shared/_milestone_badge.html.erb +22 -0
  94. data/app/views/rails_onboarding/shared/_milestone_celebration.html.erb +44 -0
  95. data/app/views/rails_onboarding/shared/_onboarding_banner.html.erb +48 -0
  96. data/app/views/rails_onboarding/templates/index.html.erb +255 -0
  97. data/config/importmap.rb +33 -0
  98. data/config/locales/en.yml +62 -0
  99. data/config/locales/es.yml +62 -0
  100. data/config/locales/fr.yml +62 -0
  101. data/config/routes.rb +79 -0
  102. data/lib/generators/rails_onboarding/install_generator.rb +124 -0
  103. data/lib/generators/rails_onboarding/templates/README +120 -0
  104. data/lib/generators/rails_onboarding/templates/add_analytics_to_rails_onboarding.rb +33 -0
  105. data/lib/generators/rails_onboarding/templates/add_milestone_tracking_to_users.rb +38 -0
  106. data/lib/generators/rails_onboarding/templates/add_onboarding_indexes.rb +93 -0
  107. data/lib/generators/rails_onboarding/templates/add_onboarding_to_users.rb +51 -0
  108. data/lib/generators/rails_onboarding/templates/add_robustness_fields_to_users.rb.tt +20 -0
  109. data/lib/generators/rails_onboarding/templates/create_rails_onboarding_flows.rb +18 -0
  110. data/lib/generators/rails_onboarding/templates/onboarding.css +18 -0
  111. data/lib/generators/rails_onboarding/templates/rails_onboarding.rb +75 -0
  112. data/lib/generators/rails_onboarding/update_generator.rb +73 -0
  113. data/lib/rails_onboarding/api_mode.rb +367 -0
  114. data/lib/rails_onboarding/backfill.rb +130 -0
  115. data/lib/rails_onboarding/background_jobs.rb +412 -0
  116. data/lib/rails_onboarding/caching.rb +180 -0
  117. data/lib/rails_onboarding/cdn_support.rb +202 -0
  118. data/lib/rails_onboarding/configuration/ab_testing.rb +24 -0
  119. data/lib/rails_onboarding/configuration/analytics.rb +29 -0
  120. data/lib/rails_onboarding/configuration/integrations.rb +31 -0
  121. data/lib/rails_onboarding/configuration/milestones.rb +92 -0
  122. data/lib/rails_onboarding/configuration/personalization.rb +25 -0
  123. data/lib/rails_onboarding/configuration/progressive_disclosure.rb +41 -0
  124. data/lib/rails_onboarding/configuration/rate_limiting.rb +18 -0
  125. data/lib/rails_onboarding/configuration/steps.rb +121 -0
  126. data/lib/rails_onboarding/configuration/templates.rb +85 -0
  127. data/lib/rails_onboarding/configuration/tooltips.rb +32 -0
  128. data/lib/rails_onboarding/configuration.rb +133 -0
  129. data/lib/rails_onboarding/configuration_errors.rb +19 -0
  130. data/lib/rails_onboarding/configuration_validator.rb +477 -0
  131. data/lib/rails_onboarding/controller_helpers.rb +153 -0
  132. data/lib/rails_onboarding/current.rb +13 -0
  133. data/lib/rails_onboarding/deprecation.rb +64 -0
  134. data/lib/rails_onboarding/devise_integration.rb +122 -0
  135. data/lib/rails_onboarding/engine.rb +245 -0
  136. data/lib/rails_onboarding/error_recovery.rb +173 -0
  137. data/lib/rails_onboarding/lazy_loading.rb +155 -0
  138. data/lib/rails_onboarding/multi_tenant.rb +306 -0
  139. data/lib/rails_onboarding/requirements_validator.rb +315 -0
  140. data/lib/rails_onboarding/responsive_helper.rb +218 -0
  141. data/lib/rails_onboarding/session_manager.rb +187 -0
  142. data/lib/rails_onboarding/skip_logic.rb +159 -0
  143. data/lib/rails_onboarding/turbo_compatibility.rb +229 -0
  144. data/lib/rails_onboarding/version.rb +3 -0
  145. data/lib/rails_onboarding.rb +44 -0
  146. data/lib/tasks/rails_onboarding_analytics.rake +136 -0
  147. data/lib/tasks/rails_onboarding_tasks.rake +162 -0
  148. metadata +303 -0
@@ -0,0 +1,120 @@
1
+ ===============================================================================
2
+
3
+ Rails Onboarding has been installed!
4
+
5
+ Next steps:
6
+
7
+ 1. Run the migrations:
8
+ bin/rails db:migrate
9
+
10
+ 2. Add the Onboardable concern to your User model:
11
+
12
+ # app/models/user.rb
13
+ class User < ApplicationRecord
14
+ include RailsOnboarding::Onboardable
15
+
16
+ # your existing code...
17
+ end
18
+
19
+ 3. Review config/initializers/rails_onboarding.rb - in particular, make sure
20
+ redirect_after_completion / redirect_after_skip point at routes that
21
+ actually exist in your app (they default to :root_path).
22
+
23
+ 4. Load the CSS and JS in your layout.
24
+
25
+ On Sprockets, rails_onboarding/application.css bundles all of the gem's
26
+ stylesheets itself, so one tag is enough:
27
+
28
+ # app/views/layouts/application.html.erb
29
+ <%= stylesheet_link_tag "rails_onboarding/application" %>
30
+ <%= javascript_include_tag "rails_onboarding/application", defer: true %>
31
+
32
+ On Propshaft (Rails 8's default, including with cssbundling-rails),
33
+ there's no bundling step, so application.css only contains its own base
34
+ styles - you need one tag per gem stylesheet or tooltips/tours/
35
+ milestones/mobile responsiveness/etc. will silently never load:
36
+
37
+ # app/views/layouts/application.html.erb
38
+ <%= stylesheet_link_tag "rails_onboarding/application" %>
39
+ <%= stylesheet_link_tag "rails_onboarding/tooltips" %>
40
+ <%= stylesheet_link_tag "rails_onboarding/utilities" %>
41
+ <%= stylesheet_link_tag "rails_onboarding/accessibility" %>
42
+ <%= stylesheet_link_tag "rails_onboarding/milestones" %>
43
+ <%= stylesheet_link_tag "rails_onboarding/tour" %>
44
+ <%= stylesheet_link_tag "rails_onboarding/flash_messages" %>
45
+ <%= stylesheet_link_tag "rails_onboarding/progressive_disclosure" %>
46
+ <%= stylesheet_link_tag "rails_onboarding/admin" %>
47
+ <%= stylesheet_link_tag "rails_onboarding/mobile" %>
48
+ <%= javascript_include_tag "rails_onboarding/application", defer: true %>
49
+
50
+ (Keep application first and mobile last; skip admin if you don't use the
51
+ admin dashboard.)
52
+
53
+ If you're on Importmap, the tags above are enough to load the tooltip/tour helpers
54
+ (RailsOnboarding.showTooltip, etc.), but the Stimulus controllers still
55
+ need to be registered explicitly - add to app/javascript/controllers/index.js:
56
+
57
+ import OnboardingController from "rails_onboarding/onboarding_controller"
58
+ import ProgressController from "rails_onboarding/progress_controller"
59
+ import NavigationController from "rails_onboarding/navigation_controller"
60
+ import TooltipController from "rails_onboarding/tooltip_controller"
61
+
62
+ application.register("onboarding", OnboardingController)
63
+ application.register("progress", ProgressController)
64
+ application.register("navigation", NavigationController)
65
+ application.register("tooltip", TooltipController)
66
+
67
+ (These four cover the core onboarding flow. See the README's Asset
68
+ Loading section for the full controller list, including milestones,
69
+ tours, and the admin dashboard.)
70
+
71
+ If you're on ESBuild/Webpack/Vite instead, see docs/ESBUILD_SETUP.md
72
+ in the gem's repository (github.com/bunnahabhain/rails_onboarding) for
73
+ how to copy and register the controllers manually.
74
+
75
+ 5. Create a view for each step in config.steps (config/initializers/
76
+ rails_onboarding.rb). The default config ships with four steps -
77
+ welcome, profile, first_action, explore - and the gem only provides a
78
+ real view for "welcome"; any step without one falls back to a generic
79
+ "this step is not yet fully configured" placeholder, which is fine for
80
+ development but not something to ship.
81
+
82
+ # app/views/rails_onboarding/onboarding/profile.html.erb
83
+ <div class="onboarding-step">
84
+ <h1>Setup Your Profile</h1>
85
+
86
+ <%= link_to "Continue", next_onboarding_path,
87
+ method: :post,
88
+ class: "btn btn-primary" %>
89
+ </div>
90
+
91
+ Repeat for each step name. See the README's "Customize Step Views"
92
+ section for a full example, including the welcome.html.erb the gem
93
+ ships as a reference.
94
+
95
+ 6. Require a `current_user` method in your controllers (from Devise or your
96
+ own auth) - RailsOnboarding reads it to know who's onboarding. Then wire
97
+ up the redirect yourself; the gem gives you the predicate, not the
98
+ before_action:
99
+
100
+ # app/controllers/application_controller.rb
101
+ class ApplicationController < ActionController::Base
102
+ before_action :redirect_to_onboarding_if_needed
103
+
104
+ private
105
+
106
+ def redirect_to_onboarding_if_needed
107
+ redirect_to onboarding_path if needs_onboarding?
108
+ end
109
+ end
110
+
111
+ Skip specific actions/controllers with:
112
+
113
+ skip_onboarding_check only: [:edit, :update] # or except: [...]
114
+
115
+ 7. Visit /onboarding to test your setup!
116
+
117
+ For more details, see the README at
118
+ https://github.com/bunnahabhain/rails_onboarding
119
+
120
+ ===============================================================================
@@ -0,0 +1,33 @@
1
+ class AddAnalyticsToRailsOnboarding < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def up
3
+ create_table :rails_onboarding_analytics_events do |t|
4
+ t.references :user, polymorphic: true, null: true
5
+ t.string :event_type, null: false
6
+ t.text :properties
7
+ t.string :session_id
8
+ t.datetime :occurred_at, null: false
9
+
10
+ t.timestamps
11
+
12
+ # Indexes for common queries and performance optimization
13
+ t.index :event_type
14
+ t.index :occurred_at
15
+ t.index :session_id
16
+
17
+ # Polymorphic association indexes (optimized for queries on specific user types)
18
+ t.index [:user_type, :user_id], name: 'index_analytics_events_on_user'
19
+ t.index [:user_type, :user_id, :event_type], name: 'index_analytics_events_on_user_and_type'
20
+ t.index [:user_type, :user_id, :occurred_at], name: 'index_analytics_events_on_user_and_date'
21
+
22
+ # Composite indexes for common analytics queries
23
+ t.index [:event_type, :occurred_at], name: 'index_analytics_events_on_type_and_date'
24
+ t.index [:user_id, :session_id], name: 'index_analytics_events_on_user_and_session'
25
+ t.index [:session_id, :occurred_at], name: 'index_analytics_events_on_session_and_date'
26
+ t.index [:event_type, :session_id], name: 'index_analytics_events_on_type_and_session'
27
+ end
28
+ end
29
+
30
+ def down
31
+ drop_table :rails_onboarding_analytics_events if table_exists?(:rails_onboarding_analytics_events)
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ class AddMilestoneTrackingToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ # No DB-level default: MySQL/MariaDB reject a literal DEFAULT on
6
+ # TEXT/BLOB/JSON columns. Onboardable already treats a nil
7
+ # milestones_achieved as an empty array in Ruby (see onboardable.rb),
8
+ # so there's nothing to work around adapter-by-adapter here.
9
+ add_column :users, :milestones_achieved, :text
10
+ add_column :users, :milestone_points, :integer, default: 0
11
+ add_column :users, :last_milestone_at, :datetime
12
+
13
+ # On PostgreSQL, build concurrently so these don't hold a write lock on a
14
+ # large, live users table; other adapters don't support that algorithm,
15
+ # so fall back to a plain index there.
16
+ add_index :users, :milestone_points, **concurrent_index_options
17
+ add_index :users, :last_milestone_at, **concurrent_index_options
18
+ end
19
+
20
+ def down
21
+ remove_index :users, :last_milestone_at if index_exists?(:users, :last_milestone_at)
22
+ remove_index :users, :milestone_points if index_exists?(:users, :milestone_points)
23
+
24
+ remove_column :users, :last_milestone_at if column_exists?(:users, :last_milestone_at)
25
+ remove_column :users, :milestone_points if column_exists?(:users, :milestone_points)
26
+ remove_column :users, :milestones_achieved if column_exists?(:users, :milestones_achieved)
27
+ end
28
+
29
+ private
30
+
31
+ def concurrent_index_options
32
+ postgresql? ? { algorithm: :concurrently } : {}
33
+ end
34
+
35
+ def postgresql?
36
+ %w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
37
+ end
38
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddOnboardingIndexes < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ disable_ddl_transaction!
5
+
6
+ def up
7
+ # Add indexes for frequently queried columns.
8
+ # Skip if index already exists. On PostgreSQL, build concurrently so these
9
+ # don't hold a write lock on a large, live users table; other adapters
10
+ # don't support that algorithm, so fall back to a plain index there.
11
+ unless index_exists?(:users, :onboarding_completed)
12
+ add_index :users, :onboarding_completed, **concurrent_index_options
13
+ end
14
+
15
+ unless index_exists?(:users, :onboarding_current_step)
16
+ add_index :users, :onboarding_current_step, **concurrent_index_options
17
+ end
18
+
19
+ unless index_exists?(:users, :onboarding_skipped)
20
+ add_index :users, :onboarding_skipped, **concurrent_index_options
21
+ end
22
+
23
+ unless index_exists?(:users, [:onboarding_completed, :created_at])
24
+ # Composite index for finding users who need onboarding
25
+ add_index :users, [:onboarding_completed, :created_at],
26
+ name: 'index_users_on_onboarding_status_and_created', **concurrent_index_options
27
+ end
28
+
29
+ unless index_exists?(:users, :onboarding_completed_at)
30
+ # Index for analytics queries on completion time
31
+ add_index :users, :onboarding_completed_at, **concurrent_index_options
32
+ end
33
+
34
+ unless index_exists?(:users, :last_milestone_at)
35
+ # Index for milestone queries
36
+ add_index :users, :last_milestone_at, **concurrent_index_options
37
+ end
38
+
39
+ # Add indexes to analytics_events table if it exists
40
+ if table_exists?(:rails_onboarding_analytics_events)
41
+ # Named distinctly from the analytics migration's own same-columns-prefixed
42
+ # indexes (index_analytics_events_on_user_and_type is [:user_type, :user_id,
43
+ # :event_type]; index_analytics_events_on_type_and_date is [:event_type,
44
+ # :occurred_at]) - reusing those names here for different column sets would
45
+ # raise a duplicate-index-name error on every fresh install.
46
+ unless index_exists?(:rails_onboarding_analytics_events, [:user_id, :event_type])
47
+ add_index :rails_onboarding_analytics_events, [:user_id, :event_type],
48
+ name: 'index_analytics_events_on_user_id_and_event_type', **concurrent_index_options
49
+ end
50
+
51
+ unless index_exists?(:rails_onboarding_analytics_events, [:event_type, :created_at])
52
+ add_index :rails_onboarding_analytics_events, [:event_type, :created_at],
53
+ name: 'index_analytics_events_on_type_and_created_at', **concurrent_index_options
54
+ end
55
+
56
+ unless index_exists?(:rails_onboarding_analytics_events, :session_id)
57
+ add_index :rails_onboarding_analytics_events, :session_id, **concurrent_index_options
58
+ end
59
+
60
+ unless index_exists?(:rails_onboarding_analytics_events, [:user_id, :session_id])
61
+ add_index :rails_onboarding_analytics_events, [:user_id, :session_id],
62
+ name: 'index_analytics_events_on_user_and_session', **concurrent_index_options
63
+ end
64
+ end
65
+ end
66
+
67
+ def down
68
+ # Remove indexes in reverse order
69
+ if table_exists?(:rails_onboarding_analytics_events)
70
+ remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_user_and_session' if index_exists?(:rails_onboarding_analytics_events, [:user_id, :session_id], name: 'index_analytics_events_on_user_and_session')
71
+ remove_index :rails_onboarding_analytics_events, :session_id if index_exists?(:rails_onboarding_analytics_events, :session_id)
72
+ remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_type_and_created_at' if index_exists?(:rails_onboarding_analytics_events, [:event_type, :created_at], name: 'index_analytics_events_on_type_and_created_at')
73
+ remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_user_id_and_event_type' if index_exists?(:rails_onboarding_analytics_events, [:user_id, :event_type], name: 'index_analytics_events_on_user_id_and_event_type')
74
+ end
75
+
76
+ remove_index :users, :last_milestone_at if index_exists?(:users, :last_milestone_at)
77
+ remove_index :users, :onboarding_completed_at if index_exists?(:users, :onboarding_completed_at)
78
+ remove_index :users, name: 'index_users_on_onboarding_status_and_created' if index_exists?(:users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created')
79
+ remove_index :users, :onboarding_skipped if index_exists?(:users, :onboarding_skipped)
80
+ remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
81
+ remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
82
+ end
83
+
84
+ private
85
+
86
+ def concurrent_index_options
87
+ postgresql? ? { algorithm: :concurrently } : {}
88
+ end
89
+
90
+ def postgresql?
91
+ %w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
92
+ end
93
+ end
@@ -0,0 +1,51 @@
1
+ class AddOnboardingToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ add_column :users, :onboarding_completed, :boolean, default: false, null: false
6
+ add_column :users, :onboarding_completed_at, :datetime
7
+ add_column :users, :onboarding_current_step, :string
8
+ add_column :users, :onboarding_skipped, :boolean, default: false
9
+
10
+ # Use jsonb for PostgreSQL, json for MySQL/MariaDB, text for others
11
+ adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
12
+ case adapter_name
13
+ when "postgresql", "postgis"
14
+ add_column :users, :feature_tooltips_shown, :jsonb, default: {}
15
+ when "mysql2", "trilogy", "mysql"
16
+ add_column :users, :feature_tooltips_shown, :json
17
+ else
18
+ # SQLite and other databases - use text with serialization
19
+ add_column :users, :feature_tooltips_shown, :text, default: "{}"
20
+ end
21
+
22
+ # Performance indexes. On PostgreSQL, build them concurrently so they
23
+ # don't hold a write lock on a large, live users table; other adapters
24
+ # don't support that algorithm, so fall back to a plain index there.
25
+ add_index :users, :onboarding_completed, **concurrent_index_options
26
+ add_index :users, :onboarding_current_step, **concurrent_index_options
27
+ add_index :users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created', **concurrent_index_options
28
+ end
29
+
30
+ def down
31
+ remove_index :users, name: 'index_users_on_onboarding_status_and_created' if index_exists?(:users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created')
32
+ remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
33
+ remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
34
+
35
+ remove_column :users, :feature_tooltips_shown if column_exists?(:users, :feature_tooltips_shown)
36
+ remove_column :users, :onboarding_skipped if column_exists?(:users, :onboarding_skipped)
37
+ remove_column :users, :onboarding_current_step if column_exists?(:users, :onboarding_current_step)
38
+ remove_column :users, :onboarding_completed_at if column_exists?(:users, :onboarding_completed_at)
39
+ remove_column :users, :onboarding_completed if column_exists?(:users, :onboarding_completed)
40
+ end
41
+
42
+ private
43
+
44
+ def concurrent_index_options
45
+ postgresql? ? { algorithm: :concurrently } : {}
46
+ end
47
+
48
+ def postgresql?
49
+ %w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ class AddRobustnessFieldsToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def up
3
+ add_column :users, :onboarding_errors, :text unless column_exists?(:users, :onboarding_errors)
4
+ add_column :users, :onboarding_failed_actions, :text unless column_exists?(:users, :onboarding_failed_actions)
5
+ add_column :users, :onboarding_session_data, :text unless column_exists?(:users, :onboarding_session_data)
6
+
7
+ # Add indexes for performance
8
+ add_index :users, :onboarding_completed unless index_exists?(:users, :onboarding_completed)
9
+ add_index :users, :onboarding_current_step unless index_exists?(:users, :onboarding_current_step)
10
+ end
11
+
12
+ def down
13
+ remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
14
+ remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
15
+
16
+ remove_column :users, :onboarding_session_data if column_exists?(:users, :onboarding_session_data)
17
+ remove_column :users, :onboarding_failed_actions if column_exists?(:users, :onboarding_failed_actions)
18
+ remove_column :users, :onboarding_errors if column_exists?(:users, :onboarding_errors)
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ class CreateRailsOnboardingFlows < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def up
3
+ create_table :rails_onboarding_flows do |t|
4
+ t.string :name, null: false
5
+ t.text :description
6
+ t.text :steps, null: false
7
+ t.boolean :active, null: false, default: false
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :rails_onboarding_flows, :active
13
+ end
14
+
15
+ def down
16
+ drop_table :rails_onboarding_flows if table_exists?(:rails_onboarding_flows)
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ /* Custom onboarding styles for your application */
2
+ /*
3
+ * This file is for customizing the onboarding appearance
4
+ * to match your application's design system.
5
+ */
6
+
7
+ /* Example customizations: */
8
+ .onboarding-container {
9
+ /* Override default styles here */
10
+ }
11
+
12
+ .onboarding-progress {
13
+ /* Customize progress bar appearance */
14
+ }
15
+
16
+ .onboarding-step {
17
+ /* Customize step appearance */
18
+ }
@@ -0,0 +1,75 @@
1
+ # RailsOnboarding configuration
2
+ #
3
+ # See https://github.com/bunnahabhain/rails_onboarding for the full option
4
+ # reference, and docs/MILESTONES_GUIDE.md / docs/ANALYTICS_GUIDE.md for the
5
+ # features configured below.
6
+ RailsOnboarding.configure do |config|
7
+ # The class name of your application's user model. Change this if your
8
+ # app uses a different name (e.g. "Account", "Member").
9
+ config.user_class_name = "User"
10
+
11
+ # Where to send users after they finish or skip onboarding. Accepts a
12
+ # route helper name (as a symbol) or a Proc that receives the user and
13
+ # returns a path.
14
+ config.redirect_after_completion = :root_path
15
+ config.redirect_after_skip = :root_path
16
+
17
+ # Feature toggles
18
+ config.enable_tooltips = true
19
+ config.enable_milestones = true
20
+ config.enable_analytics = true
21
+
22
+ # Analytics event retention/session settings. Only relevant when
23
+ # enable_analytics is true. See docs/ANALYTICS_GUIDE.md.
24
+ config.analytics_data_retention_days = 365 # how long to keep analytics events
25
+ config.analytics_session_timeout_minutes = 30 # inactivity gap that starts a new session
26
+
27
+ # Who should be sent through onboarding?
28
+ # :new_users - only users created after the gem was installed (default)
29
+ # :all_users - every user, regardless of when their account was created
30
+ # a Proc - receives the user and returns true/false, e.g.:
31
+ # config.onboarding_required_for = ->(user) { !user.admin? }
32
+ config.onboarding_required_for = :new_users
33
+
34
+ # The steps a user walks through, in order. Each step needs a unique
35
+ # `name` (used internally for progress tracking) and a `title` shown in
36
+ # the UI. Set `skippable: true` to let users jump past a step.
37
+ #
38
+ # Customize this list to match your app's onboarding flow - add, remove,
39
+ # or reorder steps as needed. If you add a step, you'll also need a
40
+ # matching view under app/views/rails_onboarding/onboarding/<name>.html.erb
41
+ # (see the generated views for examples).
42
+ config.steps = [
43
+ {
44
+ name: :welcome,
45
+ title: "Welcome",
46
+ icon: "🎉",
47
+ skippable: true
48
+ },
49
+ {
50
+ name: :profile,
51
+ title: "Setup Profile",
52
+ icon: "👤",
53
+ skippable: false
54
+ },
55
+ {
56
+ name: :explore,
57
+ title: "Explore Features",
58
+ icon: "🔍",
59
+ skippable: true
60
+ }
61
+ ]
62
+
63
+ # Contextual tooltips shown to highlight specific UI elements. The key
64
+ # is the tooltip's identifier (referenced from your views/helpers), and
65
+ # each entry configures the text, delay (ms) before it appears, and
66
+ # position relative to its target element ("top", "bottom", "left",
67
+ # "right"). Only relevant when enable_tooltips is true.
68
+ config.feature_tooltips = {
69
+ "getting_started" => {
70
+ text: "Click here to get started!",
71
+ delay: 1000,
72
+ position: "bottom"
73
+ }
74
+ }
75
+ end
@@ -0,0 +1,73 @@
1
+ require "rails/generators/base"
2
+ require "rails_onboarding/version"
3
+
4
+ module RailsOnboarding
5
+ module Generators
6
+ # Re-copies the gem's Stimulus controllers into a bundler-based host app so
7
+ # that a gem upgrade actually reaches the app. esbuild/webpack/vite apps
8
+ # bundle their own copy of these controllers (see docs/ESBUILD_SETUP.md) rather
9
+ # than importing them from the engine the way importmap apps do, so the copy
10
+ # goes stale on every gem bump unless it is re-synced. This generator is that
11
+ # re-sync step - run it after updating the gem:
12
+ #
13
+ # bin/rails generate rails_onboarding:update
14
+ # bin/rails generate rails_onboarding:update --path some/other/dir
15
+ #
16
+ # It copies the controllers flat into app/javascript/controllers (so Stimulus
17
+ # derives the flat identifiers the gem's views use - onboarding, tooltip,
18
+ # progress, ... - not prefixed rails-onboarding--* ones; see docs/ESBUILD_SETUP.md).
19
+ # The gem's application.js is intentionally NOT copied: it would clobber the
20
+ # host's own controllers/application.js entrypoint, and nothing imports it.
21
+ #
22
+ # It overwrites the vendored controllers unconditionally (that is the point -
23
+ # they are meant to mirror the gem, so there is nothing to preserve), but is
24
+ # deliberately scoped to those files ONLY: it never touches your initializer,
25
+ # migrations, rails_onboarding_custom.css, or your onboarding step views.
26
+ # Importmap apps don't need it (they import the controllers from the gem).
27
+ class UpdateGenerator < Rails::Generators::Base
28
+ source_root File.expand_path("../../../app/assets/javascripts/rails_onboarding", __dir__)
29
+
30
+ # Written into the destination so the engine's boot-time staleness check
31
+ # (see Engine.warn_if_controllers_stale) can tell which gem version the
32
+ # copied controllers came from. Keep in sync with that constant.
33
+ VERSION_MARKER = ".rails_onboarding_version".freeze
34
+
35
+ class_option :path, type: :string,
36
+ default: "app/javascript/controllers",
37
+ desc: "Destination directory for the copied Stimulus controllers"
38
+
39
+ def copy_controllers
40
+ controller_paths.each do |rel|
41
+ copy_file rel, File.join(dest_dir, rel), force: true
42
+ end
43
+ end
44
+
45
+ def stamp_version
46
+ create_file File.join(dest_dir, VERSION_MARKER),
47
+ "#{RailsOnboarding::VERSION}\n", force: true
48
+ end
49
+
50
+ def print_next_steps
51
+ say ""
52
+ say "rails_onboarding: synced #{controller_paths.size} controller(s) to " \
53
+ "#{dest_dir} (v#{RailsOnboarding::VERSION}).", :green
54
+ say "Next, re-register the controllers and rebuild your JS bundle:", :yellow
55
+ say " bin/rails stimulus:manifest:update # if you use stimulus-rails", :yellow
56
+ say " yarn build # or your bundler's build command", :yellow
57
+ end
58
+
59
+ private
60
+
61
+ def dest_dir
62
+ options[:path]
63
+ end
64
+
65
+ # Every *_controller.js the gem ships (top-level + the admin/ subfolder,
66
+ # which yields admin--* identifiers), as paths relative to source_root.
67
+ # Globbing *_controller.js deliberately excludes application.js.
68
+ def controller_paths
69
+ @controller_paths ||= Dir.glob("**/*_controller.js", base: self.class.source_root).sort
70
+ end
71
+ end
72
+ end
73
+ end