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,180 @@
1
+ <% content_for :page_title, "User Management" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%# Carries the active filters through so the export matches the table on screen.
5
+ page/per_page are deliberately left out - the export is never paginated. %>
6
+ <%= link_to "Export CSV",
7
+ export_admin_users_path(format: :csv,
8
+ **request.query_parameters.slice("search", "status", "step", "sort", "direction")),
9
+ class: "admin-btn admin-btn-secondary" %>
10
+ <% end %>
11
+
12
+ <!-- Stats Summary -->
13
+ <div class="admin-stats-grid admin-stats-compact">
14
+ <div class="admin-stat-card">
15
+ <div class="admin-stat-label">Total Users</div>
16
+ <div class="admin-stat-value"><%= number_with_delimiter(@stats[:total]) %></div>
17
+ </div>
18
+ <div class="admin-stat-card">
19
+ <div class="admin-stat-label">Completed</div>
20
+ <div class="admin-stat-value"><%= number_with_delimiter(@stats[:completed]) %></div>
21
+ </div>
22
+ <div class="admin-stat-card">
23
+ <div class="admin-stat-label">In Progress</div>
24
+ <div class="admin-stat-value"><%= number_with_delimiter(@stats[:in_progress]) %></div>
25
+ </div>
26
+ <div class="admin-stat-card">
27
+ <div class="admin-stat-label">Not Started</div>
28
+ <div class="admin-stat-value"><%= number_with_delimiter(@stats[:not_started]) %></div>
29
+ </div>
30
+ <div class="admin-stat-card">
31
+ <div class="admin-stat-label">Skipped</div>
32
+ <div class="admin-stat-value"><%= number_with_delimiter(@stats[:skipped]) %></div>
33
+ </div>
34
+ </div>
35
+
36
+ <!-- Filters and Search -->
37
+ <div class="admin-filters">
38
+ <%= form_with url: admin_users_path, method: :get, class: "admin-filter-form" do |f| %>
39
+ <div class="admin-filter-group">
40
+ <%= f.text_field :search,
41
+ placeholder: "Search by email or ID...",
42
+ value: params[:search],
43
+ class: "admin-input" %>
44
+
45
+ <%= f.select :status,
46
+ options_for_select([
47
+ ['All Statuses', ''],
48
+ ['Completed', 'completed'],
49
+ ['In Progress', 'in_progress'],
50
+ ['Not Started', 'not_started'],
51
+ ['Skipped', 'skipped']
52
+ ], params[:status]),
53
+ {},
54
+ class: "admin-select" %>
55
+
56
+ <%= f.submit "Filter", class: "admin-btn admin-btn-primary" %>
57
+ <%= link_to "Reset", admin_users_path, class: "admin-btn admin-btn-secondary" %>
58
+ </div>
59
+ <% end %>
60
+ </div>
61
+
62
+ <!-- Users Table -->
63
+ <div class="admin-card">
64
+ <div class="admin-card-body admin-card-no-padding">
65
+ <%= form_with url: bulk_action_admin_users_path, method: :post, id: "bulk-action-form" do %>
66
+ <table class="admin-table">
67
+ <thead>
68
+ <tr>
69
+ <th class="admin-table-checkbox">
70
+ <input type="checkbox" id="select-all" class="admin-checkbox">
71
+ </th>
72
+ <th>ID</th>
73
+ <th>Email</th>
74
+ <th>Status</th>
75
+ <th>Current Step</th>
76
+ <th>Progress</th>
77
+ <th>Last Activity</th>
78
+ <th class="admin-table-actions">Actions</th>
79
+ </tr>
80
+ </thead>
81
+ <tbody>
82
+ <% if @users.any? %>
83
+ <% @users.each do |user| %>
84
+ <tr>
85
+ <td class="admin-table-checkbox">
86
+ <%= check_box_tag "user_ids[]", user.id, false, class: "admin-checkbox user-checkbox" %>
87
+ </td>
88
+ <td><%= user.id %></td>
89
+ <td>
90
+ <%= user.respond_to?(:email) ? user.email : 'N/A' %>
91
+ </td>
92
+ <td>
93
+ <% if user.onboarding_completed %>
94
+ <span class="admin-badge admin-badge-success">Completed</span>
95
+ <% elsif user.onboarding_skipped %>
96
+ <span class="admin-badge admin-badge-warning">Skipped</span>
97
+ <% elsif user.onboarding_current_step %>
98
+ <span class="admin-badge admin-badge-info">In Progress</span>
99
+ <% else %>
100
+ <span class="admin-badge admin-badge-secondary">Not Started</span>
101
+ <% end %>
102
+ </td>
103
+ <td>
104
+ <%= user.onboarding_current_step&.humanize || 'N/A' %>
105
+ </td>
106
+ <td>
107
+ <% if user.respond_to?(:onboarding_progress_percentage) %>
108
+ <div class="admin-progress-bar">
109
+ <div class="admin-progress-fill" style="--admin-progress-width: <%= user.onboarding_progress_percentage %>%"></div>
110
+ </div>
111
+ <span class="admin-progress-text"><%= user.onboarding_progress_percentage %>%</span>
112
+ <% else %>
113
+ N/A
114
+ <% end %>
115
+ </td>
116
+ <td>
117
+ <%= user.updated_at ? time_ago_in_words(user.updated_at) + ' ago' : 'N/A' %>
118
+ </td>
119
+ <td class="admin-table-actions">
120
+ <%= link_to "View", admin_user_path(user), class: "admin-btn admin-btn-sm admin-btn-secondary" %>
121
+ </td>
122
+ </tr>
123
+ <% end %>
124
+ <% else %>
125
+ <tr>
126
+ <td colspan="8" class="admin-table-empty">
127
+ No users found matching your criteria
128
+ </td>
129
+ </tr>
130
+ <% end %>
131
+ </tbody>
132
+ </table>
133
+
134
+ <% if @users.any? %>
135
+ <div class="admin-bulk-actions">
136
+ <div class="admin-bulk-actions-bar">
137
+ <span class="admin-bulk-actions-count">
138
+ <span id="selected-count">0</span> users selected
139
+ </span>
140
+ <%= select_tag :bulk_action,
141
+ options_for_select([
142
+ ['Select Action', ''],
143
+ ['Reset Onboarding', 'reset_onboarding'],
144
+ ['Complete Onboarding', 'complete_onboarding']
145
+ ]),
146
+ class: "admin-select" %>
147
+ <%= submit_tag "Apply", class: "admin-btn admin-btn-primary", data: { confirm: "Are you sure?" } %>
148
+ </div>
149
+ </div>
150
+ <% end %>
151
+ <% end %>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Pagination -->
156
+ <% if @pagy.count.positive? %>
157
+ <div class="admin-pagination">
158
+ <%= raw @pagy.info_tag(item_name: "users") %>
159
+ <%= raw @pagy.series_nav if @pagy.pages > 1 %>
160
+ </div>
161
+ <% end %>
162
+
163
+ <script>
164
+ // Select all checkbox functionality
165
+ document.getElementById('select-all')?.addEventListener('change', function(e) {
166
+ const checkboxes = document.querySelectorAll('.user-checkbox');
167
+ checkboxes.forEach(cb => cb.checked = e.target.checked);
168
+ updateSelectedCount();
169
+ });
170
+
171
+ // Update selected count
172
+ document.querySelectorAll('.user-checkbox').forEach(checkbox => {
173
+ checkbox.addEventListener('change', updateSelectedCount);
174
+ });
175
+
176
+ function updateSelectedCount() {
177
+ const count = document.querySelectorAll('.user-checkbox:checked').length;
178
+ document.getElementById('selected-count').textContent = count;
179
+ }
180
+ </script>
@@ -0,0 +1,187 @@
1
+ <% content_for :page_title, "User Details ##{@user.id}" %>
2
+
3
+ <% content_for :header_actions do %>
4
+ <%= link_to "← Back to Users", admin_users_path, class: "admin-btn admin-btn-secondary" %>
5
+ <% end %>
6
+
7
+ <div class="admin-user-detail-grid">
8
+ <!-- User Info Card -->
9
+ <div class="admin-card">
10
+ <div class="admin-card-header">
11
+ <h3 class="admin-card-title">User Information</h3>
12
+ </div>
13
+ <div class="admin-card-body">
14
+ <dl class="admin-dl">
15
+ <dt>User ID</dt>
16
+ <dd><%= @user.id %></dd>
17
+
18
+ <% if @user.respond_to?(:email) %>
19
+ <dt>Email</dt>
20
+ <dd><%= @user.email %></dd>
21
+ <% end %>
22
+
23
+ <%# Users that predate the gem's install can carry NULL timestamps, so
24
+ every date on this page has to tolerate nil rather than 500. %>
25
+ <dt>Created</dt>
26
+ <dd><%= @user.created_at&.strftime('%B %d, %Y at %I:%M %p') || 'N/A' %></dd>
27
+
28
+ <dt>Last Updated</dt>
29
+ <dd><%= @user.updated_at&.strftime('%B %d, %Y at %I:%M %p') || 'N/A' %></dd>
30
+
31
+ <dt>Onboarding Status</dt>
32
+ <dd>
33
+ <% if @user.onboarding_completed %>
34
+ <span class="admin-badge admin-badge-success">Completed</span>
35
+ <% elsif @user.onboarding_skipped %>
36
+ <span class="admin-badge admin-badge-warning">Skipped</span>
37
+ <% elsif @user.onboarding_current_step %>
38
+ <span class="admin-badge admin-badge-info">In Progress</span>
39
+ <% else %>
40
+ <span class="admin-badge admin-badge-secondary">Not Started</span>
41
+ <% end %>
42
+ </dd>
43
+
44
+ <% if @user.onboarding_current_step %>
45
+ <dt>Current Step</dt>
46
+ <dd><%= @user.onboarding_current_step.humanize %></dd>
47
+ <% end %>
48
+
49
+ <% if @user.onboarding_completed_at %>
50
+ <dt>Completed At</dt>
51
+ <dd><%= @user.onboarding_completed_at.strftime('%B %d, %Y at %I:%M %p') %></dd>
52
+ <% end %>
53
+
54
+ <% if @user.respond_to?(:onboarding_progress_percentage) %>
55
+ <dt>Progress</dt>
56
+ <dd>
57
+ <div class="admin-progress-bar admin-progress-large">
58
+ <div class="admin-progress-fill" style="--admin-progress-width: <%= @user.onboarding_progress_percentage %>%"></div>
59
+ </div>
60
+ <span class="admin-progress-text"><%= @user.onboarding_progress_percentage %>%</span>
61
+ </dd>
62
+ <% end %>
63
+ </dl>
64
+ </div>
65
+ <div class="admin-card-footer">
66
+ <%= button_to "Reset Onboarding",
67
+ reset_onboarding_admin_user_path(@user),
68
+ method: :post,
69
+ class: "admin-btn admin-btn-warning",
70
+ data: { confirm: "Are you sure you want to reset this user's onboarding?" } %>
71
+ <%= button_to "Complete Onboarding",
72
+ complete_onboarding_admin_user_path(@user),
73
+ method: :post,
74
+ class: "admin-btn admin-btn-success",
75
+ data: { confirm: "Are you sure you want to mark onboarding as complete?" } %>
76
+ <%= button_to "Restart Onboarding",
77
+ restart_onboarding_admin_user_path(@user),
78
+ method: :post,
79
+ class: "admin-btn admin-btn-primary",
80
+ data: { confirm: "Are you sure you want to restart this user's onboarding from the beginning?" } %>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- Milestones Card -->
85
+ <% if @user_milestones && @user_milestones.any? %>
86
+ <div class="admin-card">
87
+ <div class="admin-card-header">
88
+ <h3 class="admin-card-title">Milestones Achieved</h3>
89
+ <span class="admin-badge"><%= @user_milestones.count %></span>
90
+ </div>
91
+ <div class="admin-card-body">
92
+ <div class="admin-list">
93
+ <% @user_milestones.each do |achievement| %>
94
+ <div class="admin-list-item">
95
+ <div class="admin-list-content">
96
+ <div class="admin-list-title">
97
+ <%= achievement.milestone.icon %> <%= achievement.milestone.title %>
98
+ </div>
99
+ <div class="admin-list-meta">
100
+ Achieved <%= time_ago_in_words(achievement.achieved_at) %> ago
101
+ </div>
102
+ </div>
103
+ <div class="admin-list-value">
104
+ <span class="admin-badge admin-badge-success">+<%= achievement.milestone.points %> pts</span>
105
+ </div>
106
+ </div>
107
+ <% end %>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <% end %>
112
+ </div>
113
+
114
+ <!-- Timeline Card (Full Width) -->
115
+ <div class="admin-card">
116
+ <div class="admin-card-header">
117
+ <h3 class="admin-card-title">Activity Timeline</h3>
118
+ </div>
119
+ <div class="admin-card-body">
120
+ <% if @user_timeline && @user_timeline.any? %>
121
+ <div class="admin-timeline admin-timeline-detailed">
122
+ <% @user_timeline.each do |item| %>
123
+ <div class="admin-timeline-item">
124
+ <div class="admin-timeline-marker">
125
+ <div class="admin-timeline-dot <%= item[:type] %>"></div>
126
+ <div class="admin-timeline-line"></div>
127
+ </div>
128
+ <div class="admin-timeline-content">
129
+ <div class="admin-timeline-header">
130
+ <h4 class="admin-timeline-title"><%= item[:description] %></h4>
131
+ <span class="admin-timeline-time"><%= time_ago_in_words(item[:timestamp]) %> ago</span>
132
+ </div>
133
+ <% if item[:metadata] && item[:metadata].any? %>
134
+ <div class="admin-timeline-metadata">
135
+ <% item[:metadata].each do |key, value| %>
136
+ <span class="admin-badge admin-badge-sm"><%= key %>: <%= value %></span>
137
+ <% end %>
138
+ </div>
139
+ <% end %>
140
+ <div class="admin-timeline-timestamp">
141
+ <%= item[:timestamp].strftime('%B %d, %Y at %I:%M:%S %p') %>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ <% end %>
146
+ </div>
147
+ <% else %>
148
+ <p class="admin-empty-state">No activity recorded for this user</p>
149
+ <% end %>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Events Card -->
154
+ <% if @user_events && @user_events.any? %>
155
+ <div class="admin-card">
156
+ <div class="admin-card-header">
157
+ <h3 class="admin-card-title">Recent Events</h3>
158
+ <span class="admin-badge"><%= @user_events.count %></span>
159
+ </div>
160
+ <div class="admin-card-body">
161
+ <table class="admin-table admin-table-sm">
162
+ <thead>
163
+ <tr>
164
+ <th>Event Type</th>
165
+ <th>Timestamp</th>
166
+ <th>Metadata</th>
167
+ </tr>
168
+ </thead>
169
+ <tbody>
170
+ <% @user_events.each do |event| %>
171
+ <tr>
172
+ <td><%= event.event_type.humanize %></td>
173
+ <td><%= event.created_at.strftime('%m/%d/%Y %I:%M %p') %></td>
174
+ <td>
175
+ <% if event.properties.present? %>
176
+ <code class="admin-code"><%= event.properties.to_json %></code>
177
+ <% else %>
178
+ -
179
+ <% end %>
180
+ </td>
181
+ </tr>
182
+ <% end %>
183
+ </tbody>
184
+ </table>
185
+ </div>
186
+ </div>
187
+ <% end %>
@@ -0,0 +1,64 @@
1
+ <%# milestonesPath is passed in rather than hardcoded in the controller: the
2
+ engine is mounted at a host-chosen prefix, so a literal "/milestones/..."
3
+ fetch 404s in every app that does not mount at root. %>
4
+ <div class="milestones-dashboard"
5
+ data-controller="milestone-dashboard"
6
+ data-milestone-dashboard-milestones-path-value="<%= milestones_path %>">
7
+ <div class="milestone-header">
8
+ <h1 class="milestone-title">🏆 Your Achievements</h1>
9
+ <div class="milestone-stats">
10
+ <div class="total-points">
11
+ <span class="points-value" data-milestone-dashboard-target="pointsValue"><%= @total_points %></span>
12
+ <span class="points-label">Total Points</span>
13
+ </div>
14
+ <div class="achievement-count">
15
+ <span class="count-value" data-milestone-dashboard-target="countValue"><%= @achieved_milestones.length %></span>
16
+ <span class="count-label">Milestones</span>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <% if @achieved_milestones.any? %>
22
+ <section class="achieved-milestones">
23
+ <h2 class="onboarding-section-title">🎉 Achieved Milestones</h2>
24
+ <div class="milestone-grid">
25
+ <% @achieved_milestones.each do |milestone| %>
26
+ <div class="milestone-card milestone-achieved" data-milestone-dashboard-target="milestoneCard">
27
+ <div class="milestone-icon"><%= milestone[:icon] %></div>
28
+ <div class="milestone-content">
29
+ <h3 class="milestone-name"><%= milestone[:title] %></h3>
30
+ <p class="milestone-description"><%= milestone[:description] %></p>
31
+ <div class="milestone-points">+<%= milestone[:points] %> points</div>
32
+ </div>
33
+ </div>
34
+ <% end %>
35
+ </div>
36
+ </section>
37
+ <% end %>
38
+
39
+ <% if @available_milestones.any? %>
40
+ <section class="available-milestones">
41
+ <h2 class="onboarding-section-title">🎯 Available Milestones</h2>
42
+ <div class="milestone-grid">
43
+ <% @available_milestones.each do |milestone| %>
44
+ <div class="milestone-card milestone-available" data-milestone-dashboard-target="milestoneCard">
45
+ <div class="milestone-icon milestone-locked"><%= milestone[:icon] %></div>
46
+ <div class="milestone-content">
47
+ <h3 class="milestone-name"><%= milestone[:title] %></h3>
48
+ <p class="milestone-description"><%= milestone[:description] %></p>
49
+ <div class="milestone-points">+<%= milestone[:points] %> points</div>
50
+ </div>
51
+ </div>
52
+ <% end %>
53
+ </div>
54
+ </section>
55
+ <% end %>
56
+
57
+ <% if @achieved_milestones.empty? && @available_milestones.empty? %>
58
+ <div class="onboarding-empty-state">
59
+ <div class="onboarding-empty-icon">🎯</div>
60
+ <h2>No milestones available</h2>
61
+ <p>Complete onboarding steps to start earning achievements!</p>
62
+ </div>
63
+ <% end %>
64
+ </div>
@@ -0,0 +1,57 @@
1
+ <div class="milestone-detail" data-controller="milestone-detail">
2
+ <div class="milestone-hero <%= 'onboarding-achieved' if @achieved %>">
3
+ <div class="milestone-icon-large">
4
+ <%= @milestone[:icon] %>
5
+ </div>
6
+ <h1 class="milestone-title"><%= @milestone[:title] %></h1>
7
+ <p class="milestone-description"><%= @milestone[:description] %></p>
8
+
9
+ <div class="milestone-meta">
10
+ <div class="milestone-points">
11
+ <span class="points-value"><%= @milestone[:points] %></span>
12
+ <span class="points-label">points</span>
13
+ </div>
14
+
15
+ <% if @achieved %>
16
+ <div class="achievement-status onboarding-achieved">
17
+ <span class="status-icon">✅</span>
18
+ <span class="status-text">Achieved</span>
19
+ </div>
20
+ <% else %>
21
+ <div class="achievement-status onboarding-pending">
22
+ <span class="status-icon">⏳</span>
23
+ <span class="status-text">Not Yet Achieved</span>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="milestone-info">
30
+ <h3>How to achieve this milestone:</h3>
31
+ <div class="achievement-requirements">
32
+ <% if @milestone[:trigger] == :onboarding_step_completed %>
33
+ <p>Complete the <strong><%= @milestone[:conditions][:step].to_s.humanize %></strong> step in onboarding.</p>
34
+ <% elsif @milestone[:trigger] == :onboarding_completed %>
35
+ <p>Complete the entire onboarding flow.</p>
36
+ <% elsif @milestone[:trigger] == :custom %>
37
+ <% if @milestone[:key] == :early_adopter %>
38
+ <p>Join within the first hour of account creation.</p>
39
+ <% else %>
40
+ <p>This milestone has custom requirements.</p>
41
+ <% end %>
42
+ <% else %>
43
+ <p>Complete specific actions to unlock this achievement.</p>
44
+ <% end %>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="milestone-actions">
49
+ <%= link_to "← Back to Achievements", milestones_path, class: "onboarding-btn onboarding-btn-secondary" %>
50
+
51
+ <% unless @achieved %>
52
+ <% if current_user.needs_onboarding? %>
53
+ <%= link_to "Continue Onboarding", onboarding_path, class: "onboarding-btn onboarding-btn-primary" %>
54
+ <% end %>
55
+ <% end %>
56
+ </div>
57
+ </div>
@@ -0,0 +1,2 @@
1
+ <%# This partial can be overridden by the host application to provide custom footer content %>
2
+ <%# Place your custom footer content here %>
@@ -0,0 +1,56 @@
1
+ <div class="progress-indicator" data-controller="progress" data-progress-value="<%= @progress %>">
2
+ <div class="onboarding-progress-bar">
3
+ <div class="onboarding-progress-fill" style="--onboarding-progress-width: <%= @progress %>%"></div>
4
+ </div>
5
+
6
+ <div class="progress-steps">
7
+ <%
8
+ # Calculate current step index (1-based)
9
+ current_step_index = @current_step ? RailsOnboarding.configuration.step_index(@current_step[:name]) + 1 : 1
10
+ %>
11
+
12
+ <% RailsOnboarding.configuration.steps.each_with_index do |step, index| %>
13
+ <% step_number = index + 1 %>
14
+ <%
15
+ step_class = "progress-step"
16
+ step_class += " onboarding-completed" if step_number < current_step_index
17
+ step_class += " onboarding-current" if step_number == current_step_index
18
+ step_class += " onboarding-upcoming" if step_number > current_step_index
19
+ %>
20
+
21
+ <div class="<%= step_class %>" data-step="<%= step_number %>">
22
+ <div class="step-marker">
23
+ <% if step_number < current_step_index %>
24
+ <span class="step-check">✓</span>
25
+ <% elsif step_number == current_step_index %>
26
+ <span class="step-number"><%= step_number %></span>
27
+ <% else %>
28
+ <span class="step-number"><%= step_number %></span>
29
+ <% end %>
30
+ </div>
31
+
32
+ <%# No .step-title here: that class is the page heading everywhere else
33
+ (application.css pairs it with .step-heading at 3xl/700), so using it
34
+ for a progress label rendered each step name at heading size. The
35
+ surrounding .step-label already supplies the intended styling. %>
36
+ <div class="step-label"><%= step[:title] %></div>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+
41
+ <div class="onboarding-progress-text">
42
+ <span class="current-progress">Step <%= current_step_index %></span>
43
+ <span class="total-steps">of <%= @total_steps %></span>
44
+
45
+ <% if RailsOnboarding.configuration.enable_milestones && defined?(current_user) && current_user %>
46
+ <div class="milestone-progress">
47
+ <span class="milestone-points">🏆 <%= current_user.total_milestone_points %> points</span>
48
+ <% if current_user.recent_milestones(limit: 1).any? %>
49
+ <span class="recent-milestone">
50
+ Latest: <%= current_user.recent_milestones(limit: 1).first[:icon] %> <%= current_user.recent_milestones(limit: 1).first[:title] %>
51
+ </span>
52
+ <% end %>
53
+ </div>
54
+ <% end %>
55
+ </div>
56
+ </div>
@@ -0,0 +1,44 @@
1
+ <nav class="step-navigation" data-controller="navigation">
2
+ <div class="navigation-buttons">
3
+ <div class="navigation-center">
4
+ <% if @current_step && @current_step[:skippable] %>
5
+ <div class="skip-options">
6
+ <%= form_with url: rails_onboarding.skip_onboarding_path, method: :post, local: true, class: "onboarding-form" do |form| %>
7
+ <%= form.submit "Skip this step", class: "secondary-action" %>
8
+ <% end %>
9
+
10
+ <%= form_with url: rails_onboarding.skip_onboarding_path(skip_all: true), method: :post, local: true, class: "onboarding-form" do |form| %>
11
+ <%= form.submit "Exit onboarding", class: "secondary-action" %>
12
+ <% end %>
13
+ </div>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="help-tooltip" data-navigation-target="helpTooltip" hidden>
19
+ <div class="tooltip-content">
20
+ <h4>Need assistance?</h4>
21
+ <p>
22
+ <% if @current_step %>
23
+ <% case @current_step[:name] %>
24
+ <% when :welcome %>
25
+ This onboarding will help you get familiar with the key features. You can skip any step that's not mandatory.
26
+ <% when :profile %>
27
+ Setting up your profile helps personalize your experience. All settings can be changed later.
28
+ <% when :first_action %>
29
+ Choose any action that interests you most - you can explore all features after onboarding.
30
+ <% when :explore %>
31
+ This overview shows you what's available. Don't worry about remembering everything!
32
+ <% else %>
33
+ Take your time with each step. You can always access help from the main menu later.
34
+ <% end %>
35
+ <% else %>
36
+ Take your time with each step. You can always access help from the main menu later.
37
+ <% end %>
38
+ </p>
39
+ <button type="button" class="close-help" data-action="click->navigation#hideHelp">
40
+ Got it
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </nav>
@@ -0,0 +1,38 @@
1
+ <div class="generic-step">
2
+ <div class="step-description">
3
+ <p class="step-intro">
4
+ <% if @current_step %>
5
+ Welcome to the <%= @current_step[:title].downcase %> step of your onboarding journey.
6
+ <% else %>
7
+ Welcome to this step of your onboarding journey.
8
+ <% end %>
9
+ </p>
10
+ </div>
11
+
12
+ <div class="step-content">
13
+ <% if @current_step %>
14
+ <div class="step-placeholder">
15
+ <h2>Step: <%= @current_step[:title] %></h2>
16
+ <p>This step is not yet fully configured. Please contact support if you see this message.</p>
17
+
18
+ <div class="step-debug">
19
+ <strong>Debug Info:</strong><br>
20
+ Step Name: <%= @current_step[:name] %><br>
21
+ Step Title: <%= @current_step[:title] %><br>
22
+ Skippable: <%= @current_step[:skippable] %><br>
23
+ Progress: <%= @progress %>/<%= @total_steps %>
24
+ </div>
25
+ </div>
26
+ <% else %>
27
+ <div class="step-error">
28
+ <h2>Configuration Error</h2>
29
+ <p>No step information is available. Please check your onboarding configuration.</p>
30
+ </div>
31
+ <% end %>
32
+ </div>
33
+
34
+ <div class="onboarding-actions">
35
+ <%= render 'step_navigation' %>
36
+ </div>
37
+ </div>
38
+ <%= render 'custom_footer' %>