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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOnboarding
|
|
4
|
+
# Validates that the host application meets all requirements for RailsOnboarding
|
|
5
|
+
# Usage: RailsOnboarding::RequirementsValidator.validate!
|
|
6
|
+
class RequirementsValidator
|
|
7
|
+
class ValidationError < StandardError; end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Run all validations and raise if any fail
|
|
11
|
+
def validate!
|
|
12
|
+
results = run_validations
|
|
13
|
+
|
|
14
|
+
if results[:errors].any?
|
|
15
|
+
raise ValidationError, format_error_message(results)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
log_results(results)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Run all validations and return results without raising
|
|
23
|
+
def check
|
|
24
|
+
run_validations
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def run_validations
|
|
30
|
+
results = {
|
|
31
|
+
errors: [],
|
|
32
|
+
warnings: [],
|
|
33
|
+
info: []
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
validate_rails_version(results)
|
|
37
|
+
validate_ruby_version(results)
|
|
38
|
+
validate_user_class(results)
|
|
39
|
+
validate_user_model(results)
|
|
40
|
+
validate_application_controller(results)
|
|
41
|
+
validate_current_user_method(results)
|
|
42
|
+
validate_database_columns(results)
|
|
43
|
+
validate_routes(results)
|
|
44
|
+
validate_optional_dependencies(results)
|
|
45
|
+
validate_configuration(results)
|
|
46
|
+
|
|
47
|
+
results
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate_rails_version(results)
|
|
51
|
+
required_version = Gem::Version.new("7.0.0")
|
|
52
|
+
current_version = Gem::Version.new(Rails.version)
|
|
53
|
+
|
|
54
|
+
if current_version < required_version
|
|
55
|
+
results[:errors] << "Rails version #{required_version} or higher required (current: #{current_version})"
|
|
56
|
+
else
|
|
57
|
+
results[:info] << "Rails version: #{current_version} ✓"
|
|
58
|
+
end
|
|
59
|
+
rescue => e
|
|
60
|
+
results[:errors] << "Could not determine Rails version: #{e.message}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_ruby_version(results)
|
|
64
|
+
required_version = Gem::Version.new("3.0.0")
|
|
65
|
+
current_version = Gem::Version.new(RUBY_VERSION)
|
|
66
|
+
|
|
67
|
+
if current_version < required_version
|
|
68
|
+
results[:errors] << "Ruby version #{required_version} or higher required (current: #{current_version})"
|
|
69
|
+
else
|
|
70
|
+
results[:info] << "Ruby version: #{current_version} ✓"
|
|
71
|
+
end
|
|
72
|
+
rescue => e
|
|
73
|
+
results[:errors] << "Could not determine Ruby version: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_user_class(results)
|
|
77
|
+
begin
|
|
78
|
+
user_class_name = RailsOnboarding.configuration.user_class_name
|
|
79
|
+
user_class = user_class_name.constantize
|
|
80
|
+
|
|
81
|
+
results[:info] << "User class '#{user_class_name}' found ✓"
|
|
82
|
+
|
|
83
|
+
# Check if Onboardable concern is included
|
|
84
|
+
unless user_class.included_modules.include?(RailsOnboarding::Onboardable)
|
|
85
|
+
results[:errors] << "User class '#{user_class_name}' must include RailsOnboarding::Onboardable concern"
|
|
86
|
+
else
|
|
87
|
+
results[:info] << "Onboardable concern included ✓"
|
|
88
|
+
end
|
|
89
|
+
rescue NameError
|
|
90
|
+
results[:errors] << "User class '#{RailsOnboarding.configuration.user_class_name}' not found"
|
|
91
|
+
rescue => e
|
|
92
|
+
results[:errors] << "Error validating user class: #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_user_model(results)
|
|
97
|
+
begin
|
|
98
|
+
user_class_name = RailsOnboarding.configuration.user_class_name
|
|
99
|
+
user_class = user_class_name.constantize
|
|
100
|
+
|
|
101
|
+
# Check for required behavioral methods (provided by the Onboardable concern).
|
|
102
|
+
# onboarding_completed? and onboarding_current_step are column-backed attribute
|
|
103
|
+
# methods instead - Rails generates those lazily on first instantiation, so
|
|
104
|
+
# instance_methods.include? can't reliably see them here; their underlying
|
|
105
|
+
# columns are already checked in validate_database_columns.
|
|
106
|
+
required_methods = [
|
|
107
|
+
:needs_onboarding?,
|
|
108
|
+
:complete_onboarding!,
|
|
109
|
+
:onboarding_progress_percentage
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
required_methods.each do |method|
|
|
113
|
+
unless user_class.instance_methods.include?(method)
|
|
114
|
+
results[:errors] << "User model missing required method: #{method}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
rescue NameError
|
|
118
|
+
# Already reported in validate_user_class
|
|
119
|
+
rescue => e
|
|
120
|
+
results[:warnings] << "Could not validate user model methods: #{e.message}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def validate_application_controller(results)
|
|
125
|
+
if defined?(ApplicationController)
|
|
126
|
+
results[:info] << "ApplicationController found ✓"
|
|
127
|
+
|
|
128
|
+
# Check if ControllerHelpers are included
|
|
129
|
+
if ApplicationController.included_modules.include?(RailsOnboarding::ControllerHelpers)
|
|
130
|
+
results[:info] << "ControllerHelpers included ✓"
|
|
131
|
+
else
|
|
132
|
+
results[:warnings] << "RailsOnboarding::ControllerHelpers not included in ApplicationController (this is automatic in engine mode)"
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
results[:warnings] << "ApplicationController not found (unusual but may be intentional)"
|
|
136
|
+
end
|
|
137
|
+
rescue => e
|
|
138
|
+
results[:warnings] << "Error checking ApplicationController: #{e.message}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate_current_user_method(results)
|
|
142
|
+
if defined?(ApplicationController)
|
|
143
|
+
if ApplicationController.instance_methods.include?(:current_user)
|
|
144
|
+
results[:info] << "current_user method available ✓"
|
|
145
|
+
else
|
|
146
|
+
results[:errors] << "ApplicationController must have a current_user method (typically provided by authentication system)"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
rescue => e
|
|
150
|
+
results[:warnings] << "Could not validate current_user method: #{e.message}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def validate_database_columns(results)
|
|
154
|
+
begin
|
|
155
|
+
user_class = RailsOnboarding.configuration.user_class_name.constantize
|
|
156
|
+
|
|
157
|
+
return unless user_class.respond_to?(:column_names)
|
|
158
|
+
|
|
159
|
+
required_columns = {
|
|
160
|
+
"onboarding_completed" => :boolean,
|
|
161
|
+
"onboarding_current_step" => :string,
|
|
162
|
+
"onboarding_skipped" => :boolean
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
optional_columns = {
|
|
166
|
+
"onboarding_completed_at" => :datetime,
|
|
167
|
+
"feature_tooltips_shown" => [ :jsonb, :text ],
|
|
168
|
+
"milestones_achieved" => [ :text, :jsonb ],
|
|
169
|
+
"milestone_points" => :integer,
|
|
170
|
+
"last_milestone_at" => :datetime
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Check required columns
|
|
174
|
+
required_columns.each do |column_name, expected_type|
|
|
175
|
+
if user_class.column_names.include?(column_name)
|
|
176
|
+
actual_type = user_class.columns_hash[column_name]&.type
|
|
177
|
+
if actual_type.to_s != expected_type.to_s
|
|
178
|
+
results[:warnings] << "Column '#{column_name}' has type #{actual_type}, expected #{expected_type}"
|
|
179
|
+
else
|
|
180
|
+
results[:info] << "Required column '#{column_name}' (#{expected_type}) ✓"
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
results[:errors] << "Required database column missing: #{column_name} (#{expected_type})"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Check optional columns
|
|
188
|
+
optional_columns.each do |column_name, expected_types|
|
|
189
|
+
expected_types = Array(expected_types)
|
|
190
|
+
if user_class.column_names.include?(column_name)
|
|
191
|
+
actual_type = user_class.columns_hash[column_name]&.type
|
|
192
|
+
if expected_types.map(&:to_s).include?(actual_type.to_s)
|
|
193
|
+
results[:info] << "Optional column '#{column_name}' (#{actual_type}) ✓"
|
|
194
|
+
else
|
|
195
|
+
results[:warnings] << "Column '#{column_name}' has type #{actual_type}, expected one of #{expected_types.join(', ')}"
|
|
196
|
+
end
|
|
197
|
+
else
|
|
198
|
+
results[:info] << "Optional column '#{column_name}' not present (some features may be unavailable)"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
rescue NameError
|
|
202
|
+
# Already reported in validate_user_class
|
|
203
|
+
rescue => e
|
|
204
|
+
results[:warnings] << "Could not validate database columns: #{e.message}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def validate_routes(results)
|
|
209
|
+
if defined?(Rails.application)
|
|
210
|
+
routes = Rails.application.routes.routes.map(&:name).compact
|
|
211
|
+
|
|
212
|
+
if routes.include?("rails_onboarding")
|
|
213
|
+
results[:info] << "Engine mounted in routes ✓"
|
|
214
|
+
else
|
|
215
|
+
results[:errors] << "RailsOnboarding engine not mounted in routes. Add: mount RailsOnboarding::Engine => '/onboarding'"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
rescue => e
|
|
219
|
+
results[:warnings] << "Could not validate routes: #{e.message}"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def validate_optional_dependencies(results)
|
|
223
|
+
# Check for optional gems
|
|
224
|
+
optional_gems = {
|
|
225
|
+
"ActiveJob" => -> { defined?(::ActiveJob::Base) },
|
|
226
|
+
"ActionMailer" => -> { defined?(::ActionMailer::Base) },
|
|
227
|
+
"Devise" => -> { defined?(Devise) },
|
|
228
|
+
"Turbo" => -> { defined?(Turbo) },
|
|
229
|
+
"Stimulus" => -> { defined?(Stimulus) },
|
|
230
|
+
"Noticed" => -> { defined?(Noticed) },
|
|
231
|
+
"ActiveModel::Serializers" => -> { defined?(ActiveModel::Serializer) }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
optional_gems.each do |gem_name, check|
|
|
235
|
+
if check.call
|
|
236
|
+
results[:info] << "Optional: #{gem_name} available ✓"
|
|
237
|
+
else
|
|
238
|
+
results[:info] << "Optional: #{gem_name} not available (some features may be limited)"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
rescue => e
|
|
242
|
+
results[:warnings] << "Error checking optional dependencies: #{e.message}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def validate_configuration(results)
|
|
246
|
+
config = RailsOnboarding.configuration
|
|
247
|
+
|
|
248
|
+
# Check critical configuration
|
|
249
|
+
unless config.steps.any?
|
|
250
|
+
results[:errors] << "No onboarding steps configured. Add steps in config/initializers/rails_onboarding.rb"
|
|
251
|
+
else
|
|
252
|
+
results[:info] << "#{config.steps.size} onboarding step(s) configured ✓"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Check step names are unique
|
|
256
|
+
step_names = config.steps.map { |s| s[:name] }
|
|
257
|
+
duplicates = step_names.select { |name| step_names.count(name) > 1 }.uniq
|
|
258
|
+
if duplicates.any?
|
|
259
|
+
results[:errors] << "Duplicate step names found: #{duplicates.join(', ')}"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Check redirect paths
|
|
263
|
+
if config.redirect_after_completion.nil?
|
|
264
|
+
results[:warnings] << "No redirect_after_completion configured (users will stay on onboarding page)"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Validate mailer configuration if emails are enabled
|
|
268
|
+
if RailsOnboarding::BackgroundJobs.background_job_options[:enable_emails] && !RailsOnboarding.action_mailer_configured?
|
|
269
|
+
results[:warnings] << "Emails enabled but ActionMailer not properly configured"
|
|
270
|
+
end
|
|
271
|
+
rescue => e
|
|
272
|
+
results[:warnings] << "Error validating configuration: #{e.message}"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def format_error_message(results)
|
|
276
|
+
message = "\n" + "=" * 80 + "\n"
|
|
277
|
+
message += "RailsOnboarding Requirements Validation Failed\n"
|
|
278
|
+
message += "=" * 80 + "\n\n"
|
|
279
|
+
|
|
280
|
+
if results[:errors].any?
|
|
281
|
+
message += "ERRORS (must be fixed):\n"
|
|
282
|
+
results[:errors].each { |error| message += " ✗ #{error}\n" }
|
|
283
|
+
message += "\n"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
if results[:warnings].any?
|
|
287
|
+
message += "WARNINGS (should be addressed):\n"
|
|
288
|
+
results[:warnings].each { |warning| message += " ⚠ #{warning}\n" }
|
|
289
|
+
message += "\n"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
message += "Please fix the errors above before using RailsOnboarding.\n"
|
|
293
|
+
message += "See documentation: https://github.com/bunnahabhain/rails_onboarding\n"
|
|
294
|
+
message += "=" * 80 + "\n"
|
|
295
|
+
|
|
296
|
+
message
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def log_results(results)
|
|
300
|
+
return unless defined?(Rails.logger)
|
|
301
|
+
|
|
302
|
+
Rails.logger.info "RailsOnboarding requirements validation passed ✓"
|
|
303
|
+
|
|
304
|
+
if results[:warnings].any?
|
|
305
|
+
Rails.logger.warn "RailsOnboarding has #{results[:warnings].size} warning(s):"
|
|
306
|
+
results[:warnings].each { |warning| Rails.logger.warn " ⚠ #{warning}" }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if results[:info].any? && Rails.env.development?
|
|
310
|
+
results[:info].each { |info| Rails.logger.debug " #{info}" }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOnboarding
|
|
4
|
+
# Responsive layout helper module
|
|
5
|
+
# Provides helper methods for implementing responsive design features in the host application
|
|
6
|
+
module ResponsiveHelper
|
|
7
|
+
# Returns the viewport meta tag for responsive design
|
|
8
|
+
# This should be included in the host application's layout
|
|
9
|
+
#
|
|
10
|
+
# @param options [Hash] Optional configuration
|
|
11
|
+
# @option options [String] :width The viewport width (default: 'device-width')
|
|
12
|
+
# @option options [Float] :initial_scale The initial zoom level (default: 1.0)
|
|
13
|
+
# @option options [Float] :maximum_scale The maximum zoom level (default: 5.0)
|
|
14
|
+
# @option options [Float] :minimum_scale The minimum zoom level (default: 1.0)
|
|
15
|
+
# @option options [Boolean] :user_scalable Whether user can zoom (default: true)
|
|
16
|
+
# @option options [String] :viewport_fit How to handle notches/safe areas (default: 'cover')
|
|
17
|
+
#
|
|
18
|
+
# @return [String] The viewport meta tag HTML
|
|
19
|
+
#
|
|
20
|
+
# @example Basic usage in a layout
|
|
21
|
+
# <%= rails_onboarding_viewport_meta %>
|
|
22
|
+
#
|
|
23
|
+
# @example Custom configuration
|
|
24
|
+
# <%= rails_onboarding_viewport_meta(maximum_scale: 3.0, viewport_fit: 'contain') %>
|
|
25
|
+
def rails_onboarding_viewport_meta(options = {})
|
|
26
|
+
width = options[:width] || "device-width"
|
|
27
|
+
initial_scale = options[:initial_scale] || 1.0
|
|
28
|
+
maximum_scale = options[:maximum_scale] || 5.0
|
|
29
|
+
minimum_scale = options[:minimum_scale] || 1.0
|
|
30
|
+
user_scalable = options.fetch(:user_scalable, true) ? "yes" : "no"
|
|
31
|
+
viewport_fit = options[:viewport_fit] || "cover"
|
|
32
|
+
|
|
33
|
+
content = [
|
|
34
|
+
"width=#{width}",
|
|
35
|
+
"initial-scale=#{initial_scale}",
|
|
36
|
+
"maximum-scale=#{maximum_scale}",
|
|
37
|
+
"minimum-scale=#{minimum_scale}",
|
|
38
|
+
"user-scalable=#{user_scalable}",
|
|
39
|
+
"viewport-fit=#{viewport_fit}"
|
|
40
|
+
].join(", ")
|
|
41
|
+
|
|
42
|
+
tag(:meta, name: "viewport", content: content)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns theme color meta tag for mobile browsers
|
|
46
|
+
#
|
|
47
|
+
# @param color [String] The theme color (default: primary color from config)
|
|
48
|
+
# @return [String] The theme-color meta tag HTML
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# <%= rails_onboarding_theme_color %>
|
|
52
|
+
# <%= rails_onboarding_theme_color('#6366f1') %>
|
|
53
|
+
def rails_onboarding_theme_color(color = nil)
|
|
54
|
+
color ||= "#6366f1" # Default primary color
|
|
55
|
+
tag(:meta, name: "theme-color", content: color)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Checks if the current device is mobile based on user agent
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean] True if mobile device
|
|
61
|
+
def mobile_device?
|
|
62
|
+
return false unless request.present?
|
|
63
|
+
|
|
64
|
+
user_agent = request.user_agent.to_s.downcase
|
|
65
|
+
mobile_patterns = [
|
|
66
|
+
/mobile/,
|
|
67
|
+
/android/,
|
|
68
|
+
/iphone/,
|
|
69
|
+
/ipad/,
|
|
70
|
+
/ipod/,
|
|
71
|
+
/blackberry/,
|
|
72
|
+
/windows phone/,
|
|
73
|
+
/opera mini/,
|
|
74
|
+
/kindle/,
|
|
75
|
+
/silk/
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
mobile_patterns.any? { |pattern| user_agent.match?(pattern) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Checks if the current device is a tablet
|
|
82
|
+
#
|
|
83
|
+
# @return [Boolean] True if tablet device
|
|
84
|
+
def tablet_device?
|
|
85
|
+
return false unless request.present?
|
|
86
|
+
|
|
87
|
+
user_agent = request.user_agent.to_s.downcase
|
|
88
|
+
tablet_patterns = [ /ipad/, /tablet/, /kindle/, /silk/, /playbook/ ]
|
|
89
|
+
|
|
90
|
+
tablet_patterns.any? { |pattern| user_agent.match?(pattern) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Checks if the current device is a phone
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean] True if phone device
|
|
96
|
+
def phone_device?
|
|
97
|
+
mobile_device? && !tablet_device?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns CSS classes for responsive context
|
|
101
|
+
#
|
|
102
|
+
# @return [String] Space-separated CSS classes
|
|
103
|
+
#
|
|
104
|
+
# @example In a view
|
|
105
|
+
# <div class="<%= responsive_device_classes %>">
|
|
106
|
+
def responsive_device_classes
|
|
107
|
+
classes = []
|
|
108
|
+
classes << "is-mobile" if mobile_device?
|
|
109
|
+
classes << "is-tablet" if tablet_device?
|
|
110
|
+
classes << "is-phone" if phone_device?
|
|
111
|
+
classes << "is-desktop" unless mobile_device?
|
|
112
|
+
classes.join(" ")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns data attributes for JavaScript device detection
|
|
116
|
+
#
|
|
117
|
+
# @return [Hash] Data attributes for HTML tags
|
|
118
|
+
#
|
|
119
|
+
# @example In a view
|
|
120
|
+
# <div <%= responsive_device_data %>>
|
|
121
|
+
def responsive_device_data
|
|
122
|
+
{
|
|
123
|
+
"data-mobile" => mobile_device?,
|
|
124
|
+
"data-tablet" => tablet_device?,
|
|
125
|
+
"data-phone" => phone_device?
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Apple-specific meta tags for iOS devices
|
|
130
|
+
#
|
|
131
|
+
# @return [String] Combined meta tags for iOS
|
|
132
|
+
#
|
|
133
|
+
# @example In a layout
|
|
134
|
+
# <%= rails_onboarding_ios_meta %>
|
|
135
|
+
def rails_onboarding_ios_meta
|
|
136
|
+
tags = []
|
|
137
|
+
|
|
138
|
+
# Enable web app capable mode
|
|
139
|
+
tags << tag(:meta, name: "apple-mobile-web-app-capable", content: "yes")
|
|
140
|
+
|
|
141
|
+
# Status bar style
|
|
142
|
+
tags << tag(:meta, name: "apple-mobile-web-app-status-bar-style", content: "default")
|
|
143
|
+
|
|
144
|
+
# Disable phone number detection
|
|
145
|
+
tags << tag(:meta, name: "format-detection", content: "telephone=no")
|
|
146
|
+
|
|
147
|
+
safe_join(tags, "\n")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns manifest link for PWA support
|
|
151
|
+
#
|
|
152
|
+
# @param manifest_path [String] Path to manifest.json
|
|
153
|
+
# @return [String] Link tag for manifest
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# <%= rails_onboarding_manifest_link %>
|
|
157
|
+
def rails_onboarding_manifest_link(manifest_path = "/manifest.json")
|
|
158
|
+
tag(:link, rel: "manifest", href: manifest_path)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Helper to render responsive images
|
|
162
|
+
#
|
|
163
|
+
# @param src [String] Base image source
|
|
164
|
+
# @param options [Hash] Image options
|
|
165
|
+
# @option options [String] :alt Alt text
|
|
166
|
+
# @option options [Hash] :srcset Responsive image sources
|
|
167
|
+
# @option options [String] :sizes Media query sizes
|
|
168
|
+
#
|
|
169
|
+
# @example
|
|
170
|
+
# <%= responsive_image('welcome.jpg',
|
|
171
|
+
# alt: 'Welcome',
|
|
172
|
+
# srcset: { '1x' => 'welcome.jpg', '2x' => 'welcome@2x.jpg' },
|
|
173
|
+
# sizes: '(max-width: 768px) 100vw, 50vw') %>
|
|
174
|
+
def responsive_image(src, options = {})
|
|
175
|
+
alt = options[:alt] || ""
|
|
176
|
+
srcset = options[:srcset]
|
|
177
|
+
sizes = options[:sizes]
|
|
178
|
+
css_class = options[:class] || ""
|
|
179
|
+
|
|
180
|
+
img_options = { src: src, alt: alt, class: css_class }
|
|
181
|
+
|
|
182
|
+
if srcset.present?
|
|
183
|
+
srcset_value = srcset.map { |density, path| "#{path} #{density}" }.join(", ")
|
|
184
|
+
img_options[:srcset] = srcset_value
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
img_options[:sizes] = sizes if sizes.present?
|
|
188
|
+
|
|
189
|
+
tag(:img, img_options)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns appropriate loading attribute for images
|
|
193
|
+
#
|
|
194
|
+
# @param eager [Boolean] Whether to load eagerly
|
|
195
|
+
# @return [String] Loading attribute value
|
|
196
|
+
def image_loading_strategy(eager: false)
|
|
197
|
+
eager ? "eager" : "lazy"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Checks if the viewport supports hover (desktop/laptop)
|
|
201
|
+
#
|
|
202
|
+
# @return [Boolean] True if hover is supported
|
|
203
|
+
# Note: This is a server-side estimation; client-side detection is more accurate
|
|
204
|
+
def supports_hover?
|
|
205
|
+
!mobile_device?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Returns touch-friendly attributes for interactive elements
|
|
209
|
+
#
|
|
210
|
+
# @return [Hash] Data attributes for touch optimization
|
|
211
|
+
def touch_friendly_attrs
|
|
212
|
+
{
|
|
213
|
+
"data-touch-enabled" => mobile_device?,
|
|
214
|
+
"style" => mobile_device? ? "touch-action: manipulation;" : nil
|
|
215
|
+
}.compact
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|