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,224 @@
1
+ <% content_for :page_title, "Dashboard" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <div class="admin-header-filters">
5
+ <%= form_with url: admin_dashboard_path, method: :get, class: "admin-filter-form" do |f| %>
6
+ <%= f.select :date_range,
7
+ options_for_select([
8
+ ['Last 7 days', '7'],
9
+ ['Last 30 days', '30'],
10
+ ['Last 90 days', '90'],
11
+ ['All time', 'all']
12
+ ], @date_range),
13
+ {},
14
+ class: "admin-select",
15
+ onchange: "this.form.submit()" %>
16
+ <% end %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <!-- Overview Stats -->
21
+ <div class="admin-stats-grid">
22
+ <div class="admin-stat-card">
23
+ <div class="admin-stat-icon">👥</div>
24
+ <div class="admin-stat-content">
25
+ <div class="admin-stat-label">Total Users</div>
26
+ <div class="admin-stat-value"><%= number_with_delimiter(@total_users) %></div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="admin-stat-card">
31
+ <div class="admin-stat-icon">🚀</div>
32
+ <div class="admin-stat-content">
33
+ <div class="admin-stat-label">Onboarding Started</div>
34
+ <div class="admin-stat-value"><%= number_with_delimiter(@onboarding_started) %></div>
35
+ <div class="admin-stat-change">
36
+ <%= (@total_users.zero? ? 0 : (@onboarding_started.to_f / @total_users * 100).round(1)) %>% of users
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="admin-stat-card">
42
+ <div class="admin-stat-icon">✅</div>
43
+ <div class="admin-stat-content">
44
+ <div class="admin-stat-label">Completed</div>
45
+ <div class="admin-stat-value"><%= number_with_delimiter(@onboarding_completed) %></div>
46
+ <div class="admin-stat-change <%= @completion_rate >= 50 ? 'onboarding-positive' : 'onboarding-negative' %>">
47
+ <%= @completion_rate %>% completion rate
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="admin-stat-card">
53
+ <div class="admin-stat-icon">⏱️</div>
54
+ <div class="admin-stat-content">
55
+ <div class="admin-stat-label">Avg. Time to Complete</div>
56
+ <div class="admin-stat-value"><%= @avg_completion_time %> hrs</div>
57
+ <div class="admin-stat-change">
58
+ <%= @recent_completions %> in selected period
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <!-- Charts Row -->
65
+ <div class="admin-charts-grid">
66
+ <!-- Completion Trend -->
67
+ <div class="admin-card">
68
+ <div class="admin-card-header">
69
+ <h3 class="admin-card-title">Daily Completions (Last 7 Days)</h3>
70
+ </div>
71
+ <div class="admin-card-body">
72
+ <% if @daily_completions && @daily_completions.any? %>
73
+ <div class="admin-chart-container">
74
+ <div class="admin-bar-chart">
75
+ <%# max_count is the bar-height divisor; clamp to >= 1 so a week
76
+ with zero completions (max == 0) doesn't divide by zero. %>
77
+ <% max_count = [@daily_completions.map { |d| d[:count] }.max.to_i, 1].max %>
78
+ <% @daily_completions.each do |day| %>
79
+ <div class="admin-bar-item">
80
+ <div class="admin-bar" style="--admin-bar-height: <%= (day[:count].to_f / max_count * 100).round %>%">
81
+ <span class="admin-bar-value"><%= day[:count] %></span>
82
+ </div>
83
+ <div class="admin-bar-label"><%= day[:date] %></div>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+ </div>
88
+ <% else %>
89
+ <p class="admin-empty-state">No completion data available</p>
90
+ <% end %>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Step Funnel -->
95
+ <div class="admin-card">
96
+ <div class="admin-card-header">
97
+ <h3 class="admin-card-title">Onboarding Funnel</h3>
98
+ </div>
99
+ <div class="admin-card-body">
100
+ <% if @step_funnel && @step_funnel.any? %>
101
+ <div class="admin-funnel">
102
+ <% @step_funnel.each_with_index do |step, index| %>
103
+ <div class="admin-funnel-step">
104
+ <div class="admin-funnel-label">
105
+ <span class="admin-funnel-step-name"><%= step[:title] %></span>
106
+ <span class="admin-funnel-stats">
107
+ <%= number_with_delimiter(step[:users]) %> users (<%= step[:percentage] %>%)
108
+ </span>
109
+ </div>
110
+ <div class="admin-funnel-track">
111
+ <div class="admin-funnel-bar" style="--admin-funnel-width: <%= step[:percentage] %>%"></div>
112
+ </div>
113
+ <% if index < @step_funnel.length - 1 %>
114
+ <div class="admin-funnel-arrow">↓</div>
115
+ <% end %>
116
+ </div>
117
+ <% end %>
118
+ </div>
119
+ <% else %>
120
+ <p class="admin-empty-state">No funnel data available</p>
121
+ <% end %>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Bottom Row -->
127
+ <div class="admin-bottom-grid">
128
+ <!-- Milestone Stats -->
129
+ <% if defined?(RailsOnboarding::Milestone) %>
130
+ <div class="admin-card">
131
+ <div class="admin-card-header">
132
+ <h3 class="admin-card-title">Top Milestones</h3>
133
+ </div>
134
+ <div class="admin-card-body">
135
+ <% if @top_milestones && @top_milestones.any? %>
136
+ <div class="admin-list">
137
+ <% @top_milestones.each do |milestone| %>
138
+ <div class="admin-list-item">
139
+ <div class="admin-list-content">
140
+ <div class="admin-list-title"><%= milestone[:title] %></div>
141
+ <div class="admin-list-meta"><%= milestone[:name] %></div>
142
+ </div>
143
+ <div class="admin-list-value">
144
+ <span class="admin-badge"><%= number_with_delimiter(milestone[:count]) %> achieved</span>
145
+ </div>
146
+ </div>
147
+ <% end %>
148
+ </div>
149
+ <% else %>
150
+ <p class="admin-empty-state">No milestone data available</p>
151
+ <% end %>
152
+ </div>
153
+ </div>
154
+ <% end %>
155
+
156
+ <!-- A/B Tests Summary -->
157
+ <% if defined?(RailsOnboarding::AbTest) %>
158
+ <div class="admin-card">
159
+ <div class="admin-card-header">
160
+ <h3 class="admin-card-title">A/B Tests</h3>
161
+ <%= link_to "View All", admin_ab_tests_path, class: "admin-card-action" %>
162
+ </div>
163
+ <div class="admin-card-body">
164
+ <div class="admin-ab-summary">
165
+ <div class="admin-ab-stat">
166
+ <div class="admin-ab-stat-value"><%= @active_tests || 0 %></div>
167
+ <div class="admin-ab-stat-label">Active Tests</div>
168
+ </div>
169
+ <div class="admin-ab-stat">
170
+ <div class="admin-ab-stat-value"><%= @total_tests || 0 %></div>
171
+ <div class="admin-ab-stat-label">Total Tests</div>
172
+ </div>
173
+ </div>
174
+ <%= link_to "Manage A/B Tests", admin_ab_tests_path, class: "admin-btn admin-btn-primary admin-btn-block" %>
175
+ </div>
176
+ </div>
177
+ <% end %>
178
+
179
+ <!-- Recent Activity -->
180
+ <% if defined?(RailsOnboarding::AnalyticsEvent) %>
181
+ <div class="admin-card">
182
+ <div class="admin-card-header">
183
+ <h3 class="admin-card-title">Recent Activity</h3>
184
+ </div>
185
+ <div class="admin-card-body">
186
+ <% if @recent_events && @recent_events.any? %>
187
+ <div class="admin-timeline">
188
+ <% @recent_events.first(5).each do |event| %>
189
+ <div class="admin-timeline-item">
190
+ <div class="admin-timeline-dot"></div>
191
+ <div class="admin-timeline-content">
192
+ <div class="admin-timeline-title"><%= event.event_type.humanize %></div>
193
+ <div class="admin-timeline-meta">
194
+ User <%= event.user_id %> • <%= time_ago_in_words(event.created_at) %> ago
195
+ </div>
196
+ </div>
197
+ </div>
198
+ <% end %>
199
+ </div>
200
+ <% else %>
201
+ <p class="admin-empty-state">No recent activity</p>
202
+ <% end %>
203
+ </div>
204
+ </div>
205
+ <% end %>
206
+ </div>
207
+
208
+ <% if @analytics_error %>
209
+ <div class="admin-alert admin-alert-warning">
210
+ <strong>Analytics Error:</strong> <%= @analytics_error %>
211
+ </div>
212
+ <% end %>
213
+
214
+ <% if @milestone_error %>
215
+ <div class="admin-alert admin-alert-warning">
216
+ <strong>Milestone Error:</strong> <%= @milestone_error %>
217
+ </div>
218
+ <% end %>
219
+
220
+ <% if @ab_test_error %>
221
+ <div class="admin-alert admin-alert-warning">
222
+ <strong>A/B Test Error:</strong> <%= @ab_test_error %>
223
+ </div>
224
+ <% end %>
@@ -0,0 +1,147 @@
1
+ <%= form_with model: @flow, url: @flow[:id] ? admin_flow_path(@flow[:id]) : admin_flows_path, method: @flow[:id] ? :patch : :post, class: "admin-form", data: { controller: "flow-editor" } do |f| %>
2
+
3
+ <!-- Basic Info -->
4
+ <div class="admin-card">
5
+ <div class="admin-card-header">
6
+ <h3 class="admin-card-title">Flow Information</h3>
7
+ </div>
8
+ <div class="admin-card-body">
9
+ <div class="admin-form-group">
10
+ <%= f.label :name, "Flow Name", class: "admin-label" %>
11
+ <%= f.text_field :name, class: "admin-input", placeholder: "e.g., SaaS Onboarding", required: true %>
12
+ </div>
13
+
14
+ <div class="admin-form-group">
15
+ <%= f.label :description, "Description", class: "admin-label" %>
16
+ <%= f.text_area :description, class: "admin-textarea", rows: 3, placeholder: "Describe this onboarding flow..." %>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <!-- Steps Configuration -->
22
+ <div class="admin-card" data-flow-editor-target="stepsContainer">
23
+ <div class="admin-card-header">
24
+ <h3 class="admin-card-title">Onboarding Steps</h3>
25
+ <button type="button" class="admin-btn admin-btn-sm admin-btn-primary" data-action="click->flow-editor#addStep">
26
+ + Add Step
27
+ </button>
28
+ </div>
29
+ <div class="admin-card-body">
30
+ <div id="steps-list" class="admin-steps-list">
31
+ <% steps = @flow[:steps] || [] %>
32
+ <% if steps.any? %>
33
+ <% steps.each_with_index do |step, index| %>
34
+ <div class="admin-step-item" data-step-index="<%= index %>">
35
+ <div class="admin-step-handle">
36
+ <span class="admin-step-drag-icon">⋮⋮</span>
37
+ <span class="admin-step-number"><%= index + 1 %></span>
38
+ </div>
39
+
40
+ <div class="admin-step-content">
41
+ <div class="admin-form-row">
42
+ <div class="admin-form-group">
43
+ <%= label_tag "flow_steps_#{index}_name", "Step Name", class: "admin-label admin-label-sm" %>
44
+ <%= text_field_tag "flow[steps][#{index}][name]", step[:name], class: "admin-input admin-input-sm", placeholder: "welcome", required: true %>
45
+ </div>
46
+
47
+ <div class="admin-form-group">
48
+ <%= label_tag "flow_steps_#{index}_title", "Display Title", class: "admin-label admin-label-sm" %>
49
+ <%= text_field_tag "flow[steps][#{index}][title]", step[:title], class: "admin-input admin-input-sm", placeholder: "Welcome", required: true %>
50
+ </div>
51
+
52
+ <div class="admin-form-group admin-form-group-sm">
53
+ <%= label_tag "flow_steps_#{index}_icon", "Icon", class: "admin-label admin-label-sm" %>
54
+ <%= select_tag "flow[steps][#{index}][icon]",
55
+ options_for_select(@available_icons.map { |icon| [icon, icon] }, step[:icon]),
56
+ class: "admin-select admin-select-sm" %>
57
+ </div>
58
+
59
+ <div class="admin-form-group admin-form-group-checkbox">
60
+ <%= label_tag "flow_steps_#{index}_skippable", class: "admin-checkbox-label" do %>
61
+ <%= check_box_tag "flow[steps][#{index}][skippable]", "1", step[:skippable], class: "admin-checkbox" %>
62
+ <span>Skippable</span>
63
+ <% end %>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="admin-form-group">
68
+ <%= label_tag "flow_steps_#{index}_description", "Description (optional)", class: "admin-label admin-label-sm" %>
69
+ <%= text_area_tag "flow[steps][#{index}][description]", step[:description], class: "admin-textarea admin-textarea-sm", rows: 2 %>
70
+ </div>
71
+
72
+ <%= hidden_field_tag "flow[steps][#{index}][order]", index %>
73
+ </div>
74
+
75
+ <div class="admin-step-actions">
76
+ <button type="button" class="admin-btn admin-btn-sm admin-btn-icon admin-btn-danger" data-action="click->flow-editor#removeStep">
77
+ 🗑️
78
+ </button>
79
+ </div>
80
+ </div>
81
+ <% end %>
82
+ <% else %>
83
+ <p class="admin-text-muted">No steps added yet. Click "Add Step" to get started.</p>
84
+ <% end %>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Actions -->
90
+ <div class="admin-form-actions">
91
+ <%= f.submit @flow[:id] ? "Update Flow" : "Create Flow", class: "admin-btn admin-btn-primary" %>
92
+ <%= link_to "Cancel", admin_flows_path, class: "admin-btn admin-btn-secondary" %>
93
+ </div>
94
+ <% end %>
95
+
96
+ <!-- Step Template (hidden, used by JavaScript) -->
97
+ <template id="step-template">
98
+ <div class="admin-step-item" data-step-index="__INDEX__">
99
+ <div class="admin-step-handle">
100
+ <span class="admin-step-drag-icon">⋮⋮</span>
101
+ <span class="admin-step-number">__NUMBER__</span>
102
+ </div>
103
+
104
+ <div class="admin-step-content">
105
+ <div class="admin-form-row">
106
+ <div class="admin-form-group">
107
+ <label class="admin-label admin-label-sm">Step Name</label>
108
+ <input type="text" name="flow[steps][__INDEX__][name]" class="admin-input admin-input-sm" placeholder="step_name" required>
109
+ </div>
110
+
111
+ <div class="admin-form-group">
112
+ <label class="admin-label admin-label-sm">Display Title</label>
113
+ <input type="text" name="flow[steps][__INDEX__][title]" class="admin-input admin-input-sm" placeholder="Step Title" required>
114
+ </div>
115
+
116
+ <div class="admin-form-group admin-form-group-sm">
117
+ <label class="admin-label admin-label-sm">Icon</label>
118
+ <select name="flow[steps][__INDEX__][icon]" class="admin-select admin-select-sm">
119
+ <% @available_icons.each do |icon| %>
120
+ <option value="<%= icon %>"><%= icon %></option>
121
+ <% end %>
122
+ </select>
123
+ </div>
124
+
125
+ <div class="admin-form-group admin-form-group-checkbox">
126
+ <label class="admin-checkbox-label">
127
+ <input type="checkbox" name="flow[steps][__INDEX__][skippable]" value="1" class="admin-checkbox">
128
+ <span>Skippable</span>
129
+ </label>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="admin-form-group">
134
+ <label class="admin-label admin-label-sm">Description (optional)</label>
135
+ <textarea name="flow[steps][__INDEX__][description]" class="admin-textarea admin-textarea-sm" rows="2"></textarea>
136
+ </div>
137
+
138
+ <input type="hidden" name="flow[steps][__INDEX__][order]" value="__INDEX__">
139
+ </div>
140
+
141
+ <div class="admin-step-actions">
142
+ <button type="button" class="admin-btn admin-btn-sm admin-btn-icon admin-btn-danger" data-action="click->flow-editor#removeStep">
143
+ 🗑️
144
+ </button>
145
+ </div>
146
+ </div>
147
+ </template>
@@ -0,0 +1,7 @@
1
+ <% content_for :page_title, "Edit Flow: #{@flow[:name]}" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%= link_to "← Back to Flows", admin_flows_path, class: "admin-btn admin-btn-secondary" %>
5
+ <% end %>
6
+
7
+ <%= render 'form' %>
@@ -0,0 +1,125 @@
1
+ <% content_for :page_title, "Onboarding Flows" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%= link_to "New Flow", new_admin_flow_path, class: "admin-btn admin-btn-primary" %>
5
+ <% end %>
6
+
7
+ <% if @active_flow %>
8
+ <div class="admin-alert admin-alert-info">
9
+ <strong>Active Flow:</strong> <%= @active_flow[:name] %>
10
+ <span class="admin-badge"><%= @active_flow[:steps]&.length || 0 %> steps</span>
11
+ </div>
12
+ <% end %>
13
+
14
+ <!-- Flows List -->
15
+ <div class="admin-flows-grid">
16
+ <% if @flows && @flows.any? %>
17
+ <% @flows.each do |flow| %>
18
+ <div class="admin-flow-card <%= flow[:active] ? 'admin-flow-active' : '' %>">
19
+ <div class="admin-flow-header">
20
+ <h3 class="admin-flow-title">
21
+ <%= flow[:name] %>
22
+ <% if flow[:active] %>
23
+ <span class="admin-badge admin-badge-success">Active</span>
24
+ <% end %>
25
+ </h3>
26
+ <p class="admin-flow-description"><%= flow[:description] %></p>
27
+ </div>
28
+
29
+ <div class="admin-flow-body">
30
+ <div class="admin-flow-stats">
31
+ <div class="admin-flow-stat">
32
+ <div class="admin-flow-stat-value"><%= flow[:steps]&.length || 0 %></div>
33
+ <div class="admin-flow-stat-label">Steps</div>
34
+ </div>
35
+ <div class="admin-flow-stat">
36
+ <div class="admin-flow-stat-value">
37
+ <%= flow[:updated_at] ? time_ago_in_words(flow[:updated_at]) : 'N/A' %>
38
+ </div>
39
+ <div class="admin-flow-stat-label">Last Modified</div>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="admin-flow-steps-preview">
44
+ <% if flow[:steps] && flow[:steps].any? %>
45
+ <div class="admin-flow-steps-list">
46
+ <% flow[:steps].first(4).each_with_index do |step, index| %>
47
+ <div class="admin-flow-step-badge">
48
+ <%= step[:icon] %> <%= step[:title] %>
49
+ </div>
50
+ <% end %>
51
+ <% if flow[:steps].length > 4 %>
52
+ <div class="admin-flow-step-more">
53
+ +<%= flow[:steps].length - 4 %> more
54
+ </div>
55
+ <% end %>
56
+ </div>
57
+ <% else %>
58
+ <p class="admin-text-muted">No steps configured</p>
59
+ <% end %>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="admin-flow-actions">
64
+ <%= link_to "View", admin_flow_path(flow[:id]), class: "admin-btn admin-btn-sm admin-btn-secondary" %>
65
+ <%= link_to "Edit", edit_admin_flow_path(flow[:id]), class: "admin-btn admin-btn-sm admin-btn-secondary" %>
66
+ <%= link_to "Preview", preview_admin_flow_path(flow[:id]), class: "admin-btn admin-btn-sm admin-btn-secondary", target: "_blank" %>
67
+
68
+ <% unless flow[:active] %>
69
+ <%= button_to "Activate",
70
+ activate_admin_flow_path(flow[:id]),
71
+ method: :post,
72
+ class: "admin-btn admin-btn-sm admin-btn-success",
73
+ data: { confirm: "Activate this flow? This will deactivate the current flow." } %>
74
+ <% end %>
75
+
76
+ <%= button_to "Duplicate",
77
+ duplicate_admin_flow_path(flow[:id]),
78
+ method: :post,
79
+ class: "admin-btn admin-btn-sm admin-btn-secondary" %>
80
+
81
+ <% unless flow[:active] %>
82
+ <%= button_to "Delete",
83
+ admin_flow_path(flow[:id]),
84
+ method: :delete,
85
+ class: "admin-btn admin-btn-sm admin-btn-danger",
86
+ data: { confirm: "Are you sure you want to delete this flow?" } %>
87
+ <% end %>
88
+ </div>
89
+ </div>
90
+ <% end %>
91
+ <% else %>
92
+ <div class="admin-empty-state-large">
93
+ <div class="admin-empty-icon">📋</div>
94
+ <h3>No Flows Created</h3>
95
+ <p>Create your first onboarding flow to get started.</p>
96
+ <%= link_to "Create Flow", new_admin_flow_path, class: "admin-btn admin-btn-primary" %>
97
+ </div>
98
+ <% end %>
99
+ </div>
100
+
101
+ <!-- Pagination -->
102
+ <% if @pagy.count.positive? %>
103
+ <div class="admin-pagination">
104
+ <%= raw @pagy.info_tag(item_name: "flows") %>
105
+ <%= raw @pagy.series_nav if @pagy.pages > 1 %>
106
+ </div>
107
+ <% end %>
108
+
109
+ <!-- Templates Section -->
110
+ <% if @templates && @templates.any? %>
111
+ <div class="admin-section">
112
+ <h2 class="admin-section-title">Pre-built Templates</h2>
113
+ <p class="admin-section-description">Start with a template designed for your use case</p>
114
+
115
+ <div class="admin-templates-grid">
116
+ <% @templates.each do |template| %>
117
+ <div class="admin-template-card">
118
+ <h4 class="admin-template-title"><%= template[:name] %></h4>
119
+ <p class="admin-template-description"><%= template[:description] %></p>
120
+ <%= link_to "Use Template", new_admin_flow_path(template: template[:id]), class: "admin-btn admin-btn-sm admin-btn-primary" %>
121
+ </div>
122
+ <% end %>
123
+ </div>
124
+ </div>
125
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <% content_for :page_title, "Create New Flow" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%= link_to "← Back to Flows", admin_flows_path, class: "admin-btn admin-btn-secondary" %>
5
+ <% end %>
6
+
7
+ <%= render 'form' %>
@@ -0,0 +1,40 @@
1
+ <div class="onboarding-step-wrapper">
2
+ <div class="admin-alert admin-alert-info admin-alert-preview">
3
+ <strong>Preview Mode:</strong> "<%= @flow.name %>" - <%= @steps.length %> step<%= 's' unless @steps.length == 1 %>.
4
+ This is a read-only walkthrough; the buttons below don't affect any real onboarding progress.
5
+ <%= link_to "Back to flow", admin_flow_path(@flow), class: "admin-card-action" %>
6
+ </div>
7
+
8
+ <% if @steps.any? %>
9
+ <% @steps.each_with_index do |step, index| %>
10
+ <div class="step-content">
11
+ <div class="step-header">
12
+ <% if step[:icon] %>
13
+ <span class="step-icon"><%= step[:icon] %></span>
14
+ <% end %>
15
+ <h2 class="step-title">
16
+ <%= step[:title] %>
17
+ <span class="admin-text-muted">(<%= index + 1 %> of <%= @steps.length %>)</span>
18
+ </h2>
19
+ </div>
20
+
21
+ <div class="step-body">
22
+ <% if step[:description].present? %>
23
+ <p class="step-description"><%= step[:description] %></p>
24
+ <% end %>
25
+
26
+ <p>
27
+ <code><%= step[:name] %></code>
28
+ <% if step[:skippable] %>
29
+ <span class="admin-badge admin-badge-warning">Skippable</span>
30
+ <% end %>
31
+ </p>
32
+ </div>
33
+ </div>
34
+ <% end %>
35
+ <% else %>
36
+ <div class="step-content">
37
+ <p class="admin-empty-state">This flow has no steps configured yet.</p>
38
+ </div>
39
+ <% end %>
40
+ </div>
@@ -0,0 +1,114 @@
1
+ <% content_for :page_title, @flow[:name] %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%= link_to "← Back to Flows", admin_flows_path, class: "admin-btn admin-btn-secondary" %>
5
+ <%= link_to "Edit", edit_admin_flow_path(@flow[:id]), class: "admin-btn admin-btn-primary" %>
6
+ <% end %>
7
+
8
+ <div class="admin-flow-detail-grid">
9
+ <!-- Flow Info -->
10
+ <div class="admin-card">
11
+ <div class="admin-card-header">
12
+ <h3 class="admin-card-title">Flow Information</h3>
13
+ <% if @flow[:active] %>
14
+ <span class="admin-badge admin-badge-success">Active</span>
15
+ <% end %>
16
+ </div>
17
+ <div class="admin-card-body">
18
+ <dl class="admin-dl">
19
+ <dt>Name</dt>
20
+ <dd><%= @flow[:name] %></dd>
21
+
22
+ <dt>Description</dt>
23
+ <dd><%= @flow[:description] || 'No description' %></dd>
24
+
25
+ <dt>Total Steps</dt>
26
+ <dd><%= @steps.length %></dd>
27
+
28
+ <dt>Created</dt>
29
+ <dd><%= @flow[:created_at] ? @flow[:created_at].strftime('%B %d, %Y') : 'N/A' %></dd>
30
+
31
+ <dt>Last Modified</dt>
32
+ <dd><%= @flow[:updated_at] ? @flow[:updated_at].strftime('%B %d, %Y') : 'N/A' %></dd>
33
+ </dl>
34
+ </div>
35
+ <div class="admin-card-footer">
36
+ <% unless @flow[:active] %>
37
+ <%= button_to "Activate Flow", activate_admin_flow_path(@flow[:id]), method: :post, class: "admin-btn admin-btn-success" %>
38
+ <% end %>
39
+ <%= link_to "Preview Flow", preview_admin_flow_path(@flow[:id]), class: "admin-btn admin-btn-secondary", target: "_blank" %>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Performance Stats -->
44
+ <% if @flow_stats && @flow_stats.any? %>
45
+ <div class="admin-card">
46
+ <div class="admin-card-header">
47
+ <h3 class="admin-card-title">Performance Metrics</h3>
48
+ </div>
49
+ <div class="admin-card-body">
50
+ <div class="admin-stats-list">
51
+ <% @flow_stats.each do |step_name, stats| %>
52
+ <div class="admin-stat-item">
53
+ <div class="admin-stat-label"><%= step_name.humanize %></div>
54
+ <div class="admin-stat-values">
55
+ <span class="admin-badge"><%= stats[:started] %> started</span>
56
+ <span class="admin-badge admin-badge-success"><%= stats[:completed] %> completed</span>
57
+ </div>
58
+ </div>
59
+ <% end %>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+
66
+ <!-- Steps List -->
67
+ <div class="admin-card">
68
+ <div class="admin-card-header">
69
+ <h3 class="admin-card-title">Onboarding Steps</h3>
70
+ <span class="admin-badge"><%= @steps.length %> steps</span>
71
+ </div>
72
+ <div class="admin-card-body">
73
+ <% if @steps.any? %>
74
+ <div class="admin-flow-steps-detail">
75
+ <% @steps.each_with_index do |step, index| %>
76
+ <div class="admin-flow-step-detail">
77
+ <div class="admin-flow-step-number"><%= index + 1 %></div>
78
+ <div class="admin-flow-step-info">
79
+ <h4 class="admin-flow-step-title">
80
+ <%= step[:icon] %> <%= step[:title] %>
81
+ </h4>
82
+ <p class="admin-flow-step-meta">
83
+ <code><%= step[:name] %></code>
84
+ <% if step[:skippable] %>
85
+ <span class="admin-badge admin-badge-sm admin-badge-warning">Skippable</span>
86
+ <% end %>
87
+ </p>
88
+ <% if step[:description].present? %>
89
+ <p class="admin-flow-step-description"><%= step[:description] %></p>
90
+ <% end %>
91
+ </div>
92
+ <% if @flow_stats && @flow_stats[step[:name].to_s] %>
93
+ <div class="admin-flow-step-stats">
94
+ <div class="admin-stat-mini">
95
+ <div class="admin-stat-mini-value"><%= @flow_stats[step[:name].to_s][:started] %></div>
96
+ <div class="admin-stat-mini-label">Started</div>
97
+ </div>
98
+ <div class="admin-stat-mini">
99
+ <div class="admin-stat-mini-value"><%= @flow_stats[step[:name].to_s][:completed] %></div>
100
+ <div class="admin-stat-mini-label">Completed</div>
101
+ </div>
102
+ </div>
103
+ <% end %>
104
+ </div>
105
+ <% if index < @steps.length - 1 %>
106
+ <div class="admin-flow-step-arrow">→</div>
107
+ <% end %>
108
+ <% end %>
109
+ </div>
110
+ <% else %>
111
+ <p class="admin-empty-state">No steps configured</p>
112
+ <% end %>
113
+ </div>
114
+ </div>