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,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOnboarding
4
+ # Concern for personalization capabilities on User models
5
+ # Adapts onboarding flows based on user type, role, or other attributes
6
+ #
7
+ # @example Include in your User model
8
+ # class User < ApplicationRecord
9
+ # include RailsOnboarding::Onboardable
10
+ # include RailsOnboarding::Personalizable
11
+ # end
12
+ #
13
+ # @example Configure personalized flows
14
+ # RailsOnboarding.configure do |config|
15
+ # config.personalization_enabled = true
16
+ # config.user_type_method = :account_type
17
+ # config.personalized_flows = {
18
+ # individual: [
19
+ # { name: :welcome, title: "Welcome", icon: "🎉" },
20
+ # { name: :profile, title: "Setup Profile", icon: "👤" }
21
+ # ],
22
+ # business: [
23
+ # { name: :welcome, title: "Welcome", icon: "🎉" },
24
+ # { name: :company, title: "Company Info", icon: "🏢" },
25
+ # { name: :team, title: "Team Setup", icon: "👥" }
26
+ # ]
27
+ # }
28
+ # end
29
+ module Personalizable
30
+ extend ActiveSupport::Concern
31
+
32
+ included do
33
+ # Callback to set personalized steps on onboarding start
34
+ before_save :apply_personalized_flow, if: -> { will_save_change_to_onboarding_current_step? && onboarding_current_step_was.nil? }
35
+ end
36
+
37
+ # Get the user's type for personalization
38
+ #
39
+ # @return [Symbol, String] The user type
40
+ #
41
+ # @example
42
+ # user.personalization_type
43
+ # # => :business
44
+ def personalization_type
45
+ return nil unless RailsOnboarding.configuration.personalization_enabled
46
+
47
+ method_name = RailsOnboarding.configuration.user_type_method
48
+ return nil unless respond_to?(method_name)
49
+
50
+ send(method_name)
51
+ end
52
+
53
+ # Get the personalized onboarding steps for this user
54
+ #
55
+ # @return [Array<Hash>] Array of step configurations
56
+ #
57
+ # @example
58
+ # user.personalized_steps
59
+ # # => [{name: :welcome, title: "Welcome"}, ...]
60
+ def personalized_steps
61
+ return RailsOnboarding.configuration.steps unless RailsOnboarding.configuration.personalization_enabled
62
+
63
+ user_type = personalization_type
64
+ return RailsOnboarding.configuration.steps unless user_type
65
+
66
+ flow = RailsOnboarding.configuration.personalized_flow(user_type)
67
+ flow || RailsOnboarding.configuration.steps
68
+ end
69
+
70
+ # Get the total number of steps in the personalized flow
71
+ #
72
+ # @return [Integer] Number of steps
73
+ def personalized_total_steps
74
+ personalized_steps.size
75
+ end
76
+
77
+ # Get a specific step from the personalized flow
78
+ #
79
+ # @param name [String, Symbol] Step name
80
+ # @return [Hash, nil] Step configuration or nil
81
+ def personalized_step_by_name(name)
82
+ return nil if name.nil?
83
+ personalized_steps.find { |s| s[:name].to_s == name.to_s }
84
+ end
85
+
86
+ # Get the index of a step in the personalized flow
87
+ #
88
+ # @param name [String, Symbol] Step name
89
+ # @return [Integer, nil] Step index or nil
90
+ def personalized_step_index(name)
91
+ return nil if name.nil?
92
+ personalized_steps.find_index { |s| s[:name].to_s == name.to_s }
93
+ end
94
+
95
+ # Get the next step in the personalized flow
96
+ #
97
+ # @return [Hash, nil] Next step configuration or nil
98
+ def personalized_next_step
99
+ return nil if onboarding_current_step.nil?
100
+
101
+ current_index = personalized_step_index(onboarding_current_step)
102
+ return nil if current_index.nil?
103
+
104
+ next_index = current_index + 1
105
+ return nil if next_index >= personalized_total_steps
106
+
107
+ personalized_steps[next_index]
108
+ end
109
+
110
+ # Get the previous step in the personalized flow
111
+ #
112
+ # @return [Hash, nil] Previous step configuration or nil
113
+ def personalized_previous_step
114
+ return nil if onboarding_current_step.nil?
115
+
116
+ current_index = personalized_step_index(onboarding_current_step)
117
+ return nil if current_index.nil? || current_index.zero?
118
+
119
+ personalized_steps[current_index - 1]
120
+ end
121
+
122
+ # Check if this is the first step in the personalized flow
123
+ #
124
+ # @return [Boolean]
125
+ def personalized_first_step?
126
+ return false if onboarding_current_step.nil?
127
+ personalized_step_index(onboarding_current_step)&.zero? || false
128
+ end
129
+
130
+ # Check if this is the last step in the personalized flow
131
+ #
132
+ # @return [Boolean]
133
+ def personalized_last_step?
134
+ return false if onboarding_current_step.nil?
135
+
136
+ current_index = personalized_step_index(onboarding_current_step)
137
+ return false if current_index.nil?
138
+
139
+ current_index == personalized_total_steps - 1
140
+ end
141
+
142
+ # Get the progress percentage for the personalized flow
143
+ #
144
+ # @return [Integer] Progress as percentage (0-100)
145
+ def personalized_progress_percentage
146
+ return 0 if onboarding_current_step.nil?
147
+
148
+ current_index = personalized_step_index(onboarding_current_step)
149
+ return 0 if current_index.nil?
150
+
151
+ ((current_index + 1).to_f / personalized_total_steps * 100).round
152
+ end
153
+
154
+ # Check if a specific feature should be shown to this user type
155
+ #
156
+ # @param feature_name [String, Symbol] Feature name
157
+ # @return [Boolean] True if feature should be shown
158
+ #
159
+ # @example
160
+ # user.show_personalized_feature?(:team_invite)
161
+ # # => true (for business users)
162
+ def show_personalized_feature?(feature_name)
163
+ return true unless RailsOnboarding.configuration.personalization_enabled
164
+
165
+ user_type = personalization_type
166
+ return true unless user_type
167
+
168
+ # Check if feature is in the personalized steps
169
+ personalized_steps.any? { |step| step[:name].to_s == feature_name.to_s }
170
+ end
171
+
172
+ # Get recommended next actions based on user type
173
+ #
174
+ # @return [Array<Hash>] Array of recommended actions
175
+ #
176
+ # @example
177
+ # user.personalized_recommendations
178
+ # # => [{title: "Invite Team", description: "...", action: :invite_team}]
179
+ def personalized_recommendations
180
+ return [] unless RailsOnboarding.configuration.personalization_enabled
181
+ return [] unless respond_to?(:personalization_recommendations)
182
+
183
+ send(:personalization_recommendations)
184
+ end
185
+
186
+ private
187
+
188
+ # Apply the personalized flow when onboarding starts
189
+ # This is called automatically before save when onboarding_current_step changes from nil
190
+ def apply_personalized_flow
191
+ return unless RailsOnboarding.configuration.personalization_enabled
192
+ return unless personalization_type
193
+
194
+ # Store the personalized flow in a custom attribute if available
195
+ if respond_to?(:personalized_flow_type=)
196
+ self.personalized_flow_type = personalization_type.to_s
197
+ end
198
+
199
+ # Track the personalization in analytics if available
200
+ if respond_to?(:track_analytics_event)
201
+ track_analytics_event(
202
+ "personalized_flow_applied",
203
+ {
204
+ user_type: personalization_type.to_s,
205
+ flow_steps: personalized_steps.map { |s| s[:name] }.join(",")
206
+ }
207
+ )
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOnboarding
4
+ # Concern for progressive disclosure of features
5
+ # Shows features gradually over time based on user behavior and progress
6
+ #
7
+ # @example Include in your User model
8
+ # class User < ApplicationRecord
9
+ # include RailsOnboarding::Onboardable
10
+ # include RailsOnboarding::ProgressiveDisclosure
11
+ # end
12
+ #
13
+ # @example Configure progressive features
14
+ # RailsOnboarding.configure do |config|
15
+ # config.progressive_disclosure_enabled = true
16
+ # config.progressive_features = [
17
+ # {
18
+ # key: :advanced_settings,
19
+ # reveal_condition: :time_based,
20
+ # delay: 7.days,
21
+ # title: "Advanced Settings",
22
+ # description: "Now that you're familiar with the basics..."
23
+ # },
24
+ # {
25
+ # key: :team_collaboration,
26
+ # reveal_condition: :action_based,
27
+ # check_method: :has_created_first_project?,
28
+ # title: "Team Collaboration",
29
+ # description: "Ready to invite your team?"
30
+ # }
31
+ # ]
32
+ # end
33
+ module ProgressiveDisclosure
34
+ extend ActiveSupport::Concern
35
+
36
+ included do
37
+ # Store revealed features as JSON
38
+ # Expected column: revealed_features (jsonb or text)
39
+ serialize :revealed_features, coder: JSON unless column_names.include?("revealed_features") && columns_hash["revealed_features"].type == :jsonb
40
+
41
+ # Callback to check and reveal features
42
+ after_save :check_progressive_features, if: :saved_change_to_onboarding_current_step?
43
+ end
44
+
45
+ # Check if a feature has been revealed to the user
46
+ #
47
+ # @param feature_key [String, Symbol] Feature key
48
+ # @return [Boolean] True if feature is revealed
49
+ #
50
+ # @example
51
+ # user.feature_revealed?(:advanced_settings)
52
+ # # => false
53
+ def feature_revealed?(feature_key)
54
+ return true unless RailsOnboarding.configuration.progressive_disclosure_enabled
55
+
56
+ features = revealed_features || []
57
+ features.include?(feature_key.to_s)
58
+ end
59
+
60
+ # Mark a feature as revealed
61
+ #
62
+ # @param feature_key [String, Symbol] Feature key
63
+ # @param metadata [Hash] Additional metadata about the reveal
64
+ # @return [Boolean] True if successfully marked as revealed
65
+ #
66
+ # @example
67
+ # user.reveal_feature(:advanced_settings, source: :automatic)
68
+ def reveal_feature(feature_key, metadata = {})
69
+ self.revealed_features ||= []
70
+ return false if revealed_features.include?(feature_key.to_s)
71
+
72
+ revealed_features << feature_key.to_s
73
+
74
+ # Track the reveal event
75
+ if respond_to?(:track_analytics_event)
76
+ track_analytics_event(
77
+ "feature_revealed",
78
+ {
79
+ feature_key: feature_key.to_s,
80
+ reveal_time: Time.current
81
+ }.merge(metadata)
82
+ )
83
+ end
84
+
85
+ save if persisted?
86
+ end
87
+
88
+ # Hide a previously revealed feature
89
+ #
90
+ # @param feature_key [String, Symbol] Feature key
91
+ # @return [Boolean] True if successfully hidden
92
+ def hide_feature(feature_key)
93
+ return false unless revealed_features
94
+
95
+ revealed_features.delete(feature_key.to_s)
96
+ save if persisted?
97
+ end
98
+
99
+ # Get all revealed features
100
+ #
101
+ # @return [Array<String>] Array of revealed feature keys
102
+ def all_revealed_features
103
+ revealed_features || []
104
+ end
105
+
106
+ # Get features that are ready to be revealed
107
+ #
108
+ # @return [Array<Hash>] Array of feature configurations ready to reveal
109
+ #
110
+ # @example
111
+ # user.features_ready_to_reveal
112
+ # # => [{key: :advanced_settings, title: "...", ...}]
113
+ def features_ready_to_reveal
114
+ return [] unless RailsOnboarding.configuration.progressive_disclosure_enabled
115
+
116
+ features = RailsOnboarding.configuration.progressive_features || []
117
+
118
+ features.select do |feature|
119
+ # Skip if already revealed
120
+ next false if feature_revealed?(feature[:key])
121
+
122
+ # Check if feature meets reveal conditions
123
+ feature_ready?(feature)
124
+ end
125
+ end
126
+
127
+ # Check if a specific feature is ready to be revealed
128
+ #
129
+ # @param feature [Hash] Feature configuration
130
+ # @return [Boolean] True if feature is ready
131
+ def feature_ready?(feature)
132
+ case feature[:reveal_condition]
133
+ when :time_based
134
+ time_based_ready?(feature)
135
+ when :action_based
136
+ action_based_ready?(feature)
137
+ when :step_based
138
+ step_based_ready?(feature)
139
+ when :milestone_based
140
+ milestone_based_ready?(feature)
141
+ when :engagement_based
142
+ engagement_based_ready?(feature)
143
+ else
144
+ false
145
+ end
146
+ rescue StandardError => e
147
+ Rails.logger.error("Error checking feature readiness: #{e.message}") if defined?(Rails)
148
+ false
149
+ end
150
+
151
+ # Reveal all features that are ready
152
+ #
153
+ # @return [Array<String>] Array of newly revealed feature keys
154
+ def reveal_ready_features!
155
+ newly_revealed = []
156
+
157
+ features_ready_to_reveal.each do |feature|
158
+ if reveal_feature(feature[:key], source: :automatic, condition: feature[:reveal_condition])
159
+ newly_revealed << feature[:key].to_s
160
+ end
161
+ end
162
+
163
+ newly_revealed
164
+ end
165
+
166
+ # Get the next feature that will be revealed
167
+ #
168
+ # @return [Hash, nil] Next feature configuration or nil
169
+ def next_feature_to_reveal
170
+ features = RailsOnboarding.configuration.progressive_features || []
171
+
172
+ features
173
+ .reject { |f| feature_revealed?(f[:key]) }
174
+ .min_by { |f| estimated_reveal_time(f) }
175
+ end
176
+
177
+ # Estimate when a feature will be revealed
178
+ #
179
+ # @param feature [Hash] Feature configuration
180
+ # @return [Time, nil] Estimated reveal time or nil
181
+ def estimated_reveal_time(feature)
182
+ case feature[:reveal_condition]
183
+ when :time_based
184
+ created_at + feature[:delay].seconds
185
+ when :step_based
186
+ nil # Can't estimate step-based reveals
187
+ else
188
+ nil
189
+ end
190
+ end
191
+
192
+ # Get a count of how many features have been revealed
193
+ #
194
+ # @return [Integer] Count of revealed features
195
+ def revealed_features_count
196
+ all_revealed_features.size
197
+ end
198
+
199
+ # Get a count of features pending reveal
200
+ #
201
+ # @return [Integer] Count of pending features
202
+ def pending_features_count
203
+ return 0 unless RailsOnboarding.configuration.progressive_disclosure_enabled
204
+
205
+ total_features = RailsOnboarding.configuration.progressive_features&.size || 0
206
+ total_features - revealed_features_count
207
+ end
208
+
209
+ private
210
+
211
+ # Check if time-based feature is ready
212
+ def time_based_ready?(feature)
213
+ return false unless created_at
214
+
215
+ delay = feature[:delay] || 0
216
+ created_at + delay.seconds <= Time.current
217
+ end
218
+
219
+ # Check if action-based feature is ready
220
+ def action_based_ready?(feature)
221
+ method_name = feature[:check_method]
222
+ return false unless method_name
223
+ return false unless respond_to?(method_name)
224
+
225
+ send(method_name)
226
+ end
227
+
228
+ # Check if step-based feature is ready
229
+ def step_based_ready?(feature)
230
+ return false unless onboarding_current_step
231
+
232
+ after_step = feature[:after_step]
233
+ return false unless after_step
234
+
235
+ current_index = RailsOnboarding.configuration.step_index(onboarding_current_step)
236
+ required_index = RailsOnboarding.configuration.step_index(after_step)
237
+
238
+ return false if current_index.nil? || required_index.nil?
239
+
240
+ current_index >= required_index
241
+ end
242
+
243
+ # Check if milestone-based feature is ready
244
+ def milestone_based_ready?(feature)
245
+ return false unless respond_to?(:earned_milestones)
246
+
247
+ required_milestone = feature[:required_milestone]
248
+ return false unless required_milestone
249
+
250
+ earned_milestones.include?(required_milestone.to_s)
251
+ end
252
+
253
+ # Check if engagement-based feature is ready
254
+ def engagement_based_ready?(feature)
255
+ return false unless respond_to?(:analytics_events)
256
+
257
+ min_events = feature[:min_events] || 0
258
+ event_type = feature[:event_type]
259
+
260
+ if event_type
261
+ analytics_events.where(event_type: event_type).count >= min_events
262
+ else
263
+ analytics_events.count >= min_events
264
+ end
265
+ rescue StandardError
266
+ false
267
+ end
268
+
269
+ # Callback to check and reveal features after step changes
270
+ def check_progressive_features
271
+ return unless RailsOnboarding.configuration.progressive_disclosure_enabled
272
+
273
+ reveal_ready_features!
274
+ end
275
+ end
276
+ end