mumuki-laboratory 9.20.0 → 9.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -0
  3. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +1 -3
  4. data/app/controllers/appendixes_controller.rb +7 -0
  5. data/app/controllers/application_controller.rb +6 -3
  6. data/app/controllers/complements_controller.rb +13 -0
  7. data/app/controllers/login_controller.rb +11 -3
  8. data/app/controllers/users_controller.rb +11 -4
  9. data/app/helpers/application_helper.rb +3 -1
  10. data/app/helpers/discussions_helper.rb +41 -26
  11. data/app/helpers/mailer_helper.rb +5 -0
  12. data/app/helpers/user_menu_helper.rb +7 -3
  13. data/app/mailers/application_mailer.rb +1 -0
  14. data/app/mailers/user_mailer.rb +6 -1
  15. data/app/views/book/show.html.erb +21 -18
  16. data/app/views/chapters/show.html.erb +1 -1
  17. data/app/views/discussions/_description_message.html.erb +6 -4
  18. data/app/views/discussions/_message.html.erb +10 -8
  19. data/app/views/discussions/show.html.erb +1 -1
  20. data/app/views/errors/forbidden.html.erb +1 -3
  21. data/app/views/errors/gone.html.erb +1 -2
  22. data/app/views/errors/not_found.html.erb +1 -1
  23. data/app/views/guides/_guide.html.erb +9 -5
  24. data/app/views/layouts/_discussions.html.erb +1 -3
  25. data/app/views/layouts/_organization_chooser.html.erb +2 -2
  26. data/app/views/layouts/_user_menu.html.erb +2 -0
  27. data/app/views/user_mailer/delete_account.html.erb +7 -0
  28. data/app/views/users/_user_delete_confirmation.erb +17 -0
  29. data/app/views/users/_user_delete_modal.html.erb +35 -0
  30. data/app/views/users/delete_account.html.erb +27 -0
  31. data/app/views/users/delete_confirmation_invalid.html.erb +22 -0
  32. data/app/views/users/delete_request.html.erb +1 -0
  33. data/config/routes.rb +6 -3
  34. data/lib/mumuki/laboratory/controllers/dynamic_errors.rb +6 -6
  35. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +5 -1
  36. data/lib/mumuki/laboratory/locales/en.yml +19 -2
  37. data/lib/mumuki/laboratory/locales/es-CL.yml +20 -3
  38. data/lib/mumuki/laboratory/locales/es.yml +21 -3
  39. data/lib/mumuki/laboratory/locales/pt.yml +20 -4
  40. data/lib/mumuki/laboratory/version.rb +1 -1
  41. data/spec/capybara_helper.rb +10 -10
  42. data/spec/controllers/discussions_messages_controller_spec.rb +5 -5
  43. data/spec/controllers/messages_controller_spec.rb +3 -3
  44. data/spec/controllers/users_controller_spec.rb +20 -1
  45. data/spec/dummy/config/environments/development.rb +8 -2
  46. data/spec/dummy/config/environments/test.rb +4 -1
  47. data/spec/dummy/db/schema.rb +5 -2
  48. data/spec/features/discussion_flow_spec.rb +2 -2
  49. data/spec/features/dynamic_exam_spec.rb +4 -2
  50. data/spec/features/guides_flow_spec.rb +29 -7
  51. data/spec/features/immersive_redirection_spec.rb +2 -2
  52. data/spec/features/not_found_public_flow_spec.rb +8 -1
  53. data/spec/features/profile_flow_spec.rb +3 -1
  54. data/spec/features/read_only_flow_spec.rb +72 -1
  55. data/spec/mailers/previews/user_mailer_preview.rb +4 -0
  56. data/spec/mailers/user_mailer_spec.rb +10 -3
  57. metadata +133 -108
  58. data/spec/features/disable_user_flow_spec.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de7e211f8a772b15e0018a75d15c69f1fd455bf4794362235e236c06bc125d06
4
- data.tar.gz: 91b6b7564068e6fac29130c62639a17c277d0954fbf3900e07e947ae3cdc6da0
3
+ metadata.gz: b9a44eb8a52ea3e076a0e6dd62ba634664458f2010b8df7d5a9d36f2dc20d9fe
4
+ data.tar.gz: 16995ef8fe178a5e8f4ba5d9454e55628f7a17de7c61a61625fae017eb6beecd
5
5
  SHA512:
6
- metadata.gz: 63dc960e1d91bd05a534d8ded2bfb9cfd13801ee2d3dfcb44da781642bcfa626f0bf80c7df642216bb7915a1ff2f32515668c6bb81b7615221e23ae44a3cd627
7
- data.tar.gz: 7011fe8f2abadae8c93ac60192c0caa200a475a229183c257ccd74c0818291c9ced49507a75fc15f092405d9c10d8cad269b6723be45eac41dc022a120c7bd6e
6
+ metadata.gz: b7b567eb9a6b759b11c2d471bed6a5221e0c1c0123e7513c70111df499169714ea05ae2c26d5c86eeaf3a3ac1f85d490b3a9d529717834c865b15bc777a3d9b0
7
+ data.tar.gz: f7c010004b5dff4fd790e89bb6701277e7d3c8bc7fbf12a7c411aecf0bb437f36defbcc911381140ac74d2f101be83859bda2ce15726fdd7b2d53b8c8e5c9d7a
data/README.md CHANGED
@@ -205,6 +205,17 @@ o.reindex_usages!
205
205
 
206
206
  Now you will be able to visit that guide at `http://localhost:3000/central/guides/#{slug}`
207
207
 
208
+ ## Debugging email sender
209
+
210
+ The development environment is configured to "send" emails via `mailcatcher`, a mock server, if it is available. Run these commands to install and run it - and do it _before_ the emails are sent, so it can actually _catch_ them:
211
+
212
+ ```bash
213
+ gem install mailcatcher
214
+ mailcatcher
215
+ ```
216
+
217
+ Once up and running, go to http://localhost:1080/ to see which emails have been sent. Unfortunately, the developers recommend not to install it via Bundler, so it has to be done this way. :woman_shrugging:
218
+
208
219
  ## JavaScript API Docs
209
220
 
210
221
  In order to be customized by runners, Laboratory exposes the following selectors and methods
@@ -318,7 +318,6 @@ $moderator-badge-color: #dd9900;
318
318
  margin-bottom: 20px;
319
319
  .discussion-message-bubble-header {
320
320
  background-color: $mu-color-highlight-background;
321
- height: 40px;
322
321
  &:before {
323
322
  position: absolute;
324
323
  top: calc(20px - #{$discussion-message-arrow-size + 2px} / 2);
@@ -330,12 +329,11 @@ $moderator-badge-color: #dd9900;
330
329
  }
331
330
  .discussion-message-bubble-title {
332
331
  padding: 5px 15px;
333
- display: block;
332
+ display: flex;
334
333
  .message-date {
335
334
  font-size: 15px;
336
335
  }
337
336
  .actions {
338
- float: right;
339
337
  > a, .dropdown {
340
338
  margin-left: 20px;
341
339
  cursor: pointer;
@@ -1,5 +1,12 @@
1
1
  class AppendixesController < ApplicationController
2
+
2
3
  def show
3
4
  @chapter = Chapter.find(params[:chapter_id])
4
5
  end
6
+
7
+ private
8
+
9
+ def authorization_minimum_role
10
+ :ex_student
11
+ end
5
12
  end
@@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base
7
7
  include Mumukit::Login::AuthenticationHelpers
8
8
 
9
9
  include Mumuki::Laboratory::Controllers::Authorization
10
- include Mumuki::Laboratory::Controllers::Disabling
11
10
  include Mumuki::Laboratory::Controllers::Notifications
12
11
  include Mumuki::Laboratory::Controllers::DynamicErrors
13
12
  include Mumuki::Laboratory::Controllers::EmbeddedMode
@@ -18,7 +17,6 @@ class ApplicationController < ActionController::Base
18
17
  before_action :set_locale!
19
18
  before_action :set_time_zone!
20
19
 
21
- before_action :ensure_user_enabled!, if: :current_user?
22
20
  before_action :redirect_to_proper_context!, if: :immersive_context_wrong?
23
21
 
24
22
  before_action :authorize_if_private!
@@ -44,7 +42,8 @@ class ApplicationController < ActionController::Base
44
42
  :theme_stylesheet_url,
45
43
  :extension_javascript_url,
46
44
  :current_immersive_path,
47
- :current_access_mode
45
+ :current_access_mode,
46
+ :limited_query?
48
47
 
49
48
  add_flash_types :info
50
49
 
@@ -170,4 +169,8 @@ class ApplicationController < ActionController::Base
170
169
  current_user.restore_organization_progress!(Organization.current)
171
170
  end
172
171
  end
172
+
173
+ def limited_query?
174
+ params[:limit].present?
175
+ end
173
176
  end
@@ -1,8 +1,21 @@
1
1
  class ComplementsController < GuideContainerController
2
+ include Mumuki::Laboratory::Controllers::ValidateAccessMode
2
3
 
3
4
  private
4
5
 
5
6
  def subject
6
7
  @complement ||= Complement.find_by(id: params[:id])
7
8
  end
9
+
10
+ def authorization_minimum_role
11
+ :ex_student
12
+ end
13
+
14
+ def subject_container
15
+ subject.guide
16
+ end
17
+
18
+ def contentless_subject?
19
+ subject_container.exercises.empty?
20
+ end
8
21
  end
@@ -2,9 +2,17 @@ class LoginController < ApplicationController
2
2
  Mumukit::Login.configure_login_controller! self
3
3
 
4
4
  skip_before_action :verify_authenticity_token, if: lambda { Rails.env.development? }
5
- skip_before_action :validate_user_profile!
6
- skip_before_action :validate_accepted_role_terms!
7
- skip_before_action :validate_active_organization!
5
+ skip_before_action :validate_user_profile!,
6
+ :validate_accepted_role_terms!,
7
+ :validate_active_organization!,
8
+ :redirect_to_proper_context!,
9
+ :ensure_restore_progress!,
10
+ :visit_organization!
11
+
12
+ def logout_current_user!
13
+ flash.keep
14
+ super
15
+ end
8
16
 
9
17
  private
10
18
 
@@ -47,11 +47,18 @@ class UsersController < ApplicationController
47
47
  @exam_authorization_requests ||= ExamAuthorizationRequest.where(user: current_user, organization: Organization.current)
48
48
  end
49
49
 
50
- def unsubscribe
51
- user_id = User.unsubscription_verifier.verify(params[:id])
52
- User.find(user_id).unsubscribe_from_reminders!
50
+ def send_delete_confirmation_email
51
+ current_user.generate_delete_account_token!
52
+ UserMailer.delete_account(current_user).post!
53
+ redirect_to delete_request_user_path
54
+ end
55
+
56
+ def delete_confirmation
57
+ return redirect_to delete_confirmation_invalid_user_path unless @user.delete_account_token_matches? params[:token]
58
+
59
+ @user.destroy!
53
60
 
54
- redirect_to root_path, notice: t(:unsubscribed_successfully)
61
+ redirect_to logout_path, notice: I18n.t(:user_deleted_successfully)
55
62
  end
56
63
 
57
64
  def permissible_params
@@ -17,7 +17,9 @@ module ApplicationHelper
17
17
  end
18
18
 
19
19
  def paginate(object, options = {})
20
- super(object, {theme: 'bootstrap-5', pagination_class: 'flex-wrap justify-content-center'}.merge(options))
20
+ unless limited_query?
21
+ super(object, {theme: 'bootstrap-5', pagination_class: 'flex-wrap justify-content-center'}.merge(options))
22
+ end
21
23
  end
22
24
 
23
25
  def last_box_class(trailing_boxes)
@@ -38,14 +38,14 @@ module DiscussionsHelper
38
38
 
39
39
  def solve_discussion_params_for(user)
40
40
  if user&.moderator_here?
41
- {status: :pending_review, sort: :responses_count_asc, requires_moderator_response: true}
41
+ {status: :pending_review, sort: :responses_count_asc, requires_attention: true}
42
42
  else
43
43
  {status: :opened, sort: :responses_count_desc}
44
44
  end
45
45
  end
46
46
 
47
47
  def default_discussions_params
48
- {status: :solved, sort: :upvotes_count_desc}
48
+ {status: :solved, sort: :created_at_desc, recent: true, limit: 15}
49
49
  end
50
50
 
51
51
  def user_avatar(user, image_class='')
@@ -53,37 +53,37 @@ module DiscussionsHelper
53
53
  end
54
54
 
55
55
  def forum_terms_link
56
- %Q{
56
+ <<~HTML.html_safe
57
57
  <span>
58
58
  #{ t(:forum_terms_link, terms_link: link_to_forum_terms).html_safe }
59
59
  </span>
60
- }.html_safe
60
+ HTML
61
61
  end
62
62
 
63
63
  def discussion_messages_count(discussion)
64
- %Q{
64
+ <<~HTML.html_safe
65
65
  <span class="discussion-messages-count">
66
66
  #{fa_icon :comments, type: :regular, text: discussion.messages_count}
67
67
  </span>
68
- }.html_safe
68
+ HTML
69
69
  end
70
70
 
71
71
  def discussion_validated_messages_count(discussion)
72
- %Q{
72
+ <<~HTML.html_safe
73
73
  <span class="discussion-validated-messages-count">
74
74
  #{fa_icon :comment, type: :regular}#{fa_icon :check, text: discussion.validated_messages_count}
75
75
  </span>
76
- }.html_safe
76
+ HTML
77
77
  end
78
78
 
79
79
  def discussion_upvotes_icon(discussion)
80
80
  if discussion.upvotes_count > 0
81
- %Q{
81
+ <<~HTML.html_safe
82
82
  <span class="discussion-icon fa-stack fa-xs">
83
83
  <i class="far fa-star fa-stack-2x"></i>
84
84
  <i class="fas fa-stack-1x">#{discussion.upvotes_count}</i>
85
85
  </span>
86
- }.html_safe
86
+ HTML
87
87
  end
88
88
  end
89
89
 
@@ -99,16 +99,21 @@ module DiscussionsHelper
99
99
  end
100
100
 
101
101
  def new_discussion_link(teaser_text, link_text)
102
- %Q{
102
+ <<~HTML.html_safe
103
103
  <h4>
104
104
  <span>#{t(teaser_text)}</span>
105
105
  #{link_to t(link_text), new_exercise_discussion_path(@debatable, anchor: 'new-discussion-description-container') }
106
106
  </h4>
107
- }.html_safe
107
+ HTML
108
108
  end
109
109
 
110
- def discussion_count_for_status(status, discussions)
111
- discussions.scoped_query_by(discussion_filter_params, excluded_params: [:status], excluded_methods: [:page]).by_status(status).count
110
+ def discussion_status_counts(discussions)
111
+ discussions.scoped_query_by(discussion_filter_params, excluded_params: [:status], excluded_methods: [:page])
112
+ .group(:status)
113
+ .reorder('')
114
+ .pluck(:status, 'count(*)')
115
+ .to_h
116
+ .transform_keys(&:to_sym)
112
117
  end
113
118
 
114
119
  def discussions_reset_query_link
@@ -122,11 +127,21 @@ module DiscussionsHelper
122
127
  #TODO: this one uses a long method chain in order to take advantage of eager load
123
128
  # Delegate it once again when polymorphic association is removed
124
129
  def discussions_languages(discussions)
125
- @languages ||= discussions.map { |it| it.exercise.language.name }.uniq
130
+ @languages ||= discussions.distinct
131
+ .joins(:exercise)
132
+ .pluck('languages.name')
126
133
  end
127
134
 
128
- def discussion_status_filter_link(status, discussions)
129
- discussions_count = discussion_count_for_status(status, discussions)
135
+ def discussion_status_filter_links(discussions)
136
+ status_counts = discussion_status_counts(discussions)
137
+
138
+ discussions_statuses.map do |status|
139
+ discussion_status_filter_link(status, status_counts)
140
+ end.compact.join("\n").html_safe
141
+ end
142
+
143
+ def discussion_status_filter_link(status, status_counts)
144
+ discussions_count = status_counts[status.to_sym] || 0
130
145
  if status.should_be_shown?(discussions_count, current_user)
131
146
  discussion_filter_item(:status, status) do
132
147
  discussion_status_filter(status, discussions_count)
@@ -135,17 +150,17 @@ module DiscussionsHelper
135
150
  end
136
151
 
137
152
  def discussion_status_filter(status, discussions_count)
138
- %Q{
139
- #{discussion_status_fa_icon(status)}
153
+ <<~HTML.html_safe
154
+ #{discussion_status_fa_icon(status)}
140
155
  <span>
141
156
  #{t("#{status}_count", count: discussions_count)}
142
157
  </span>
143
- }.html_safe
158
+ HTML
144
159
  end
145
160
 
146
161
  def discussion_dropdown_filter(label, filters, can_select_all = false, &block)
147
162
  if filters.present?
148
- %Q{
163
+ <<~HTML.html_safe
149
164
  <div class="dropdown discussions-toolbar-filter">
150
165
  <a id="dropdown-#{label}" data-bs-toggle="dropdown" role="menu">
151
166
  #{t label} #{fa_icon :'caret-down', class: 'fa-xs'}
@@ -155,7 +170,7 @@ module DiscussionsHelper
155
170
  #{discussion_filter_list(label, filters, &block)}
156
171
  </ul>
157
172
  </div>
158
- }.html_safe
173
+ HTML
159
174
  end
160
175
  end
161
176
 
@@ -244,7 +259,7 @@ module DiscussionsHelper
244
259
  end
245
260
 
246
261
  def discussion_delete_message_dropdown(discussion, message)
247
- %Q{
262
+ <<~HTML.html_safe
248
263
  <span class="dropdown">
249
264
  #{content_tag :span, fa_icon('trash-alt', type: :regular, class: 'fa-lg'), role: 'menu', 'data-bs-toggle': 'dropdown',
250
265
  class: 'discussion-delete-message', id: 'deleteDiscussionDropdown'}
@@ -254,17 +269,17 @@ module DiscussionsHelper
254
269
  #{discussion_delete_message_option discussion, message, :discloses_personal_information, 'user-tag'}
255
270
  </ul>
256
271
  </span>
257
- }.html_safe
272
+ HTML
258
273
  end
259
274
 
260
275
  def discussion_delete_message_option(discussion, message, motive, icon)
261
- %Q{
276
+ <<~HTML.html_safe
262
277
  <li>
263
278
  #{link_to fa_icon(icon, text: t("deletion_motive.#{motive}.present"), class: 'fa-fw fixed-icon'),
264
279
  discussion_message_path(discussion, message, motive: motive), method: :delete, class: 'dropdown-item',
265
280
  role: 'menuitem', data: { confirm: t(:are_you_sure, action: t(:destroy_message)) } }
266
281
  </li>
267
- }.html_safe
282
+ HTML
268
283
  end
269
284
 
270
285
  def message_deleted_text(message)
@@ -0,0 +1,5 @@
1
+ module MailerHelper
2
+ def delete_account_url_for(user)
3
+ @organization.url_for("/user/delete_confirmation?token=#{user.delete_account_token}")
4
+ end
5
+ end
@@ -35,11 +35,15 @@ module UserMenuHelper
35
35
  user_menu_item t(:notifications), notifications_user_path, 'notifications'
36
36
  end
37
37
 
38
+ def delete_account_user_menu_link
39
+ user_menu_item t(:delete_account), delete_account_user_path, 'delete_account', 'text-danger'
40
+ end
41
+
38
42
  private
39
43
 
40
- def user_menu_item(label, path, active_on)
41
- link_klass = 'active' if action_name == active_on
42
- content_tag :div, link_to(label, path, { class: link_klass }.compact), class: 'mu-user-menu-item'
44
+ def user_menu_item(label, path, active_on, link_klass = '')
45
+ active_klass = 'active' if action_name == active_on
46
+ content_tag :div, link_to(label, path, { class: "#{link_klass} #{active_klass}" }.compact), class: 'mu-user-menu-item'
43
47
  end
44
48
 
45
49
  def user_menu_header_icon
@@ -1,5 +1,6 @@
1
1
  class ApplicationMailer < ActionMailer::Base
2
2
  default from: Rails.configuration.reminder_sender_email
3
+ add_template_helper MailerHelper
3
4
  layout 'mailer'
4
5
 
5
6
  def self.mailer_environment_variables
@@ -22,6 +22,12 @@ class UserMailer < ApplicationMailer
22
22
  end
23
23
  end
24
24
 
25
+ def delete_account(user)
26
+ with_locale(user) do
27
+ build_email t(:delete_account_mumuki), 'delete_account'
28
+ end
29
+ end
30
+
25
31
  def certificate(certificate)
26
32
  with_locale certificate.user, certificate.organization do
27
33
  attachments[certificate.filename] = pdf_for(certificate)
@@ -39,7 +45,6 @@ class UserMailer < ApplicationMailer
39
45
 
40
46
  def with_locale(user, organization = nil, &block)
41
47
  @user = user
42
- @unsubscribe_code = User.unsubscription_verifier.generate(user.id)
43
48
  @organization = organization || user.last_organization
44
49
 
45
50
  I18n.with_locale(@organization.locale, &block)
@@ -17,30 +17,33 @@
17
17
  <% end %>
18
18
  <% end %>
19
19
 
20
- <% if show_content?(@book) %>
21
- <h2><%= t(:chapters) %></h2>
22
- <% end %>
20
+ <% unless @book.chapters.empty? %>
21
+ <% if show_content?(@book) %>
22
+ <h2><%= t(:chapters) %></h2>
23
+ <% end %>
23
24
 
24
- <% @book.chapter_visibilities_in(current_workspace).each do |it, enabled| %>
25
+ <% @book.chapter_visibilities_in(current_workspace).each do |it, enabled| %>
25
26
 
26
- <% next unless show_content?(it.topic) %>
27
+ <% next unless show_content?(it.topic) %>
27
28
 
28
- <div class="chapter-container">
29
- <div class="chapter <%= enabled ? '' : 'mu-locked' %>">
30
- <h3><%= it.number %>. <%= link_to_path_element it, mode: :plain %></h3>
31
- <div class="text-box" <%= 'aria-label=""' unless enabled %>>
32
- <%= it.description_teaser_html %>
29
+ <div class="chapter-container">
30
+ <div class="chapter <%= enabled ? '' : 'mu-locked' %>">
31
+ <h3><%= it.number %>. <%= link_to_path_element it, mode: :plain %></h3>
32
+ <div class="text-box" <%= 'aria-label=""' unless enabled %>>
33
+ <%= it.description_teaser_html %>
34
+ </div>
33
35
  </div>
34
- </div>
35
36
 
36
- <% unless enabled %>
37
- <div class="text-center mu-lock">
38
- <i class="fas fa-lock fa-5x"></i>
39
- <p><%= t :locked_content %></p>
40
- </div>
41
- <% end %>
42
- </div>
37
+ <% unless enabled %>
38
+ <div class="text-center mu-lock">
39
+ <i class="fas fa-lock fa-5x"></i>
40
+ <p><%= t :locked_content %></p>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
43
45
  <% end %>
46
+
44
47
  <% if current_user? && @exams.present? %>
45
48
  <h2><%= t(:exams) %></h2>
46
49
  <% @exams.each_with_index do |it, index| %>
@@ -20,7 +20,7 @@
20
20
  </div>
21
21
  </div>
22
22
 
23
- <% if @chapter.lessons.present? %>
23
+ <% unless @chapter.lessons.empty? %>
24
24
  <% if @chapter.monolesson? %>
25
25
  <div> <%= render partial: 'guides/guide', locals: { subject: @monolesson } %> </div>
26
26
  <% else %>
@@ -2,10 +2,12 @@
2
2
  <div class="discussion-message-bubble">
3
3
  <div class="discussion-message-bubble-header">
4
4
  <div class="discussion-message-bubble-title">
5
- <%= linked_discussion_user_name(discussion.initiator) %>
6
- <span class="message-date">
7
- <%= friendly_time(discussion.created_at, :time_since) %>
8
- </span>
5
+ <div class="flex-fill">
6
+ <%= linked_discussion_user_name(discussion.initiator) %>
7
+ <span class="message-date">
8
+ <%= friendly_time(discussion.created_at, :time_since) %>
9
+ </span>
10
+ </div>
9
11
  </div>
10
12
  </div>
11
13
  <div class="discussion-message-bubble-content">
@@ -2,14 +2,16 @@
2
2
  <div class="discussion-message-bubble">
3
3
  <div class="discussion-message-bubble-header">
4
4
  <div class="discussion-message-bubble-title">
5
- <%= linked_discussion_user_name user %>
6
- <% if user.moderator_here? %>
7
- <span class="moderator-badge"><%= t(:moderation) %></span>
8
- <% end %>
9
- <span class="message-date">
10
- <%= friendly_time(message.created_at, :time_since) %>
11
- </span>
12
- <span class="actions">
5
+ <div class="flex-fill">
6
+ <%= linked_discussion_user_name user %>
7
+ <% if message.from_moderator? %>
8
+ <span class="moderator-badge"><%= t(:moderation) %></span>
9
+ <% end %>
10
+ <span class="message-date">
11
+ <%= friendly_time(message.created_at, :time_since) %>
12
+ </span>
13
+ </div>
14
+ <span class="actions flex-shrink-0">
13
15
  <% if message.authorized?(current_user) && !message.deleted? %>
14
16
  <% if current_user&.moderator_here? %>
15
17
  <a class="discussion-message-approved <%= 'selected' if message.approved? %>"
@@ -26,7 +26,7 @@
26
26
  <%= render partial: 'discussions/description_message', locals: { discussion: @discussion } %>
27
27
  <% end %>
28
28
  <% @discussion.visible_messages.each do |message| %>
29
- <%= render partial: 'discussions/message', locals: { user: message.sender_user, message: message } %>
29
+ <%= render partial: 'discussions/message', locals: { user: message.sender, message: message } %>
30
30
  <% end %>
31
31
  <% if @discussion.commentable_by?(current_user) %>
32
32
  <hr class="message-divider">
@@ -8,12 +8,10 @@
8
8
  <%= t(:error_description, error: link_to_status_codes(403)).html_safe %>
9
9
  </p>
10
10
  <p class="mu-error-explanation-line">
11
- <%= Organization.current.explain_error(403, explanation).html_safe %>
11
+ <%= Organization.current.explain_error(error_code, explanation).html_safe %>
12
12
  </p>
13
13
  <p class="mu-error-contact-line mu-maybe">
14
14
  <%= t(:contact_administrator, link: mail_to_administrator).html_safe %>
15
15
  </p>
16
16
  </div>
17
17
  <% end %>
18
-
19
-
@@ -8,8 +8,7 @@
8
8
  <%= t(:error_description, error: link_to_status_codes(410)).html_safe %>
9
9
  </p>
10
10
  <p class="mu-error-explanation-line">
11
- <%= Organization.current.explain_error(410, explanation).html_safe %>
11
+ <%= Organization.current.explain_error(error_code, explanation).html_safe %>
12
12
  </p>
13
13
  </div>
14
14
  <% end %>
15
-
@@ -8,7 +8,7 @@
8
8
  <%= t(:error_description, error: link_to_error_404).html_safe %>
9
9
  </p>
10
10
  <p class="mu-error-explanation-line">
11
- <%= Organization.current.explain_error(404, :not_found_explanation).html_safe %>
11
+ <%= Organization.current.explain_error(:not_found, :not_found_explanation).html_safe %>
12
12
  </p>
13
13
  </div>
14
14
  <% end %>
@@ -8,12 +8,16 @@
8
8
 
9
9
  <%= yield if block_given? %>
10
10
 
11
- <h3>
12
- <%= t :exercises %>
13
- <%= restart_guide_link(@guide) if current_user && @stats.started? && @guide.resettable? %>
14
- </h3>
11
+ <% unless @guide.exercises.empty? %>
15
12
 
16
- <%= render partial: 'layouts/progress_listing', locals: { guide: @guide } %>
13
+ <h3>
14
+ <%= t :exercises %>
15
+ <%= restart_guide_link(@guide) if current_user && @stats.started? && @guide.resettable? %>
16
+ </h3>
17
+
18
+ <%= render partial: 'layouts/progress_listing', locals: { guide: @guide } %>
19
+
20
+ <% end %>
17
21
 
18
22
  <% if @stats&.done? %>
19
23
  <div class="text-box">
@@ -3,9 +3,7 @@
3
3
  <div class="discussions-toolbar">
4
4
  <div class="discussions-toolbar-status">
5
5
  <div class="d-none d-lg-block">
6
- <% discussions_statuses.each do |status| %>
7
- <%= discussion_status_filter_link(status, @discussions) %>
8
- <% end %>
6
+ <%= discussion_status_filter_links(@discussions) %>
9
7
  </div>
10
8
  </div>
11
9
  <div>
@@ -1,4 +1,4 @@
1
- <div class="modal fade mu-organization-chooser" id="redirect" tabindex="-1" role="dialog" aria-hidden="true">
1
+ <div class="modal fade mu-organization-chooser" id="organization-chooser-modal" tabindex="-1" role="dialog" aria-hidden="true">
2
2
  <div class="modal-dialog">
3
3
  <div class="modal-content">
4
4
  <div class="modal-header">
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  </div>
16
16
  <script>
17
- $("#redirect").modal({
17
+ $("#organization-chooser-modal").modal({
18
18
  backdrop: 'static',
19
19
  keyboard: false
20
20
  });
@@ -12,6 +12,8 @@
12
12
  <%= activity_user_menu_link %>
13
13
  <%= certificates_user_menu_link %>
14
14
  <%= exam_authorizations_user_menu_link %>
15
+ <%= user_menu_divider %>
16
+ <%= delete_account_user_menu_link %>
15
17
  </div>
16
18
  </div>
17
19
  <div class="mu-user-menu-divider vertical d-none d-md-block"></div>
@@ -0,0 +1,7 @@
1
+ <%= render partial: 'user_mailer/mail_template', locals: {
2
+ title: t('mailer.title.delete_account'),
3
+ subtitle: t('mailer.subtitle.delete_account'),
4
+ text: t('mailer.text.delete_account').html_safe,
5
+ button: t('mailer.button.delete_account'),
6
+ url: delete_account_url_for(@user)
7
+ } %>
@@ -0,0 +1,17 @@
1
+ <%= content_for :breadcrumbs do %>
2
+ <%= breadcrumbs @user %>
3
+ <% end %>
4
+
5
+ <div class="row">
6
+ <div class="mu-inline-block-left">
7
+ <h1>
8
+ <%= t :email_sent %>
9
+ </h1>
10
+ </div>
11
+ </div>
12
+
13
+ <div class="row">
14
+ <div class="col-md-12">
15
+ <%= t :delete_account_confirmation_email_explain_html, user_email: @user.email, disable_email: Rails.configuration.disable_email %>
16
+ </div>
17
+ </div>