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,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,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' %>
|