mumuki-laboratory 9.0.1 → 9.0.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/certificate.js +17 -0
  3. data/app/assets/javascripts/mumuki_laboratory/application/faqs.js +80 -0
  4. data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +2 -0
  5. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_certificate.scss +33 -0
  6. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +62 -0
  7. data/app/controllers/certificates_controller.rb +28 -0
  8. data/app/controllers/concerns/with_certificate_render.rb +25 -0
  9. data/app/controllers/faqs_controller.rb +6 -0
  10. data/app/controllers/users_controller.rb +4 -0
  11. data/app/helpers/certificate_helper.rb +13 -0
  12. data/app/helpers/links_helper.rb +8 -0
  13. data/app/helpers/menu_bar_helper.rb +14 -10
  14. data/app/helpers/user_menu_helper.rb +4 -0
  15. data/app/mailers/application_mailer.rb +0 -1
  16. data/app/mailers/user_mailer.rb +9 -0
  17. data/app/views/certificates/_certificate.html.erb +44 -0
  18. data/app/views/certificates/_download.html.erb +20 -0
  19. data/app/views/certificates/verify.html.erb +40 -0
  20. data/app/views/faqs/index.html.erb +16 -0
  21. data/app/views/layouts/_user_menu.html.erb +4 -0
  22. data/app/views/layouts/application.html.erb +6 -1
  23. data/app/views/layouts/exercise_inputs/editors/_code.html.erb +2 -1
  24. data/app/views/user_mailer/certificate.html.erb +339 -0
  25. data/app/views/user_mailer/certificate.text.erb +10 -0
  26. data/app/views/users/certificates.html.erb +32 -0
  27. data/config/routes.rb +6 -0
  28. data/lib/mumuki/laboratory/locales/en.yml +7 -0
  29. data/lib/mumuki/laboratory/locales/es-CL.yml +7 -0
  30. data/lib/mumuki/laboratory/locales/es.yml +7 -0
  31. data/lib/mumuki/laboratory/locales/pt.yml +7 -0
  32. data/lib/mumuki/laboratory/version.rb +1 -1
  33. data/spec/controllers/certificates_controller_spec.rb +15 -0
  34. data/spec/dummy/db/schema.rb +2 -1
  35. data/spec/features/certificate_programs_flow_spec.rb +17 -0
  36. data/spec/features/menu_bar_spec.rb +20 -0
  37. data/spec/features/profile_flow_spec.rb +12 -0
  38. data/spec/helpers/certificate_helper_spec.rb +15 -0
  39. metadata +67 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4fc890303f1598f34b8258b73ca92a3beb1d594cbdb7f00d729401df4e44f89
4
- data.tar.gz: 8ad9aba5c0b5d11251a972f2f5354f6f3ee6c6fcb3ef62d72b592e0d6e78b66f
3
+ metadata.gz: 3bfbac4c5667f84f0ef0365430b3b8b22bb421d0149a906bff69f7ad8116200a
4
+ data.tar.gz: b3a2468082c345db35724748e1e1d992e1a7fd0e4688a480040e873ac5d94650
5
5
  SHA512:
6
- metadata.gz: 3f44cc0d2df94e3b613abea57d4ff8a1c48628a2d3fbc1797cb2a2fdca1d54c1c639c8da8e19ddfb65c581f874927758cb1fbf673cd9cf66ed8c1b9186a5d9ca
7
- data.tar.gz: c5e6c4b580ec812c0ff84290558f6439fab546dc2be009da77c032b9059700230da3db4e1fc0765a4560d846d6409d4e6a17f79dbbff830a10199ad4ae25a6ec
6
+ metadata.gz: 228622d76f975543bac60f7b62f3f1ff28fb831934b1841e2fe1e4faaf6ed31b4301d30815c160c1aaa0b0751df34b056eec22ae0e56fc660261949deb40dd34
7
+ data.tar.gz: a5e86b47de449d68553f162bafa77a69c7dc850bbb65efa3ab1c1401c3091da5979453b50d306a1d8a95973ffc29a66ce6f6b9bd2c29e29192c83c0a68d887e3
@@ -0,0 +1,17 @@
1
+ mumuki.load(() => {
2
+ scaleCertificate();
3
+ mumuki.resize(scaleCertificate);
4
+
5
+ function scaleCertificate() {
6
+ const $certPreview = $('.certificate-preview');
7
+ const $muCertificate = $('.mu-certificate-box');
8
+ $muCertificate.css('transform', 'scale(1)');
9
+
10
+ const scaleWidth = $muCertificate.width() / $certPreview.width();
11
+ $muCertificate.css({
12
+ 'transform': `scale(${1 / scaleWidth})`,
13
+ 'transform-origin': `0 0`,
14
+ });
15
+ $certPreview.height($muCertificate.height());
16
+ }
17
+ });
@@ -0,0 +1,80 @@
1
+ mumuki.faqs = class {
2
+
3
+ constructor() {
4
+ this.faqs = $('.mu-faqs');
5
+ this.topHierarchyElem = "H2";
6
+ }
7
+
8
+ // ================
9
+ // == Public API ==
10
+ // ================
11
+
12
+ /**
13
+ * Actually setup the faqs page
14
+ */
15
+ load() {
16
+ if(!this.faqs) return;
17
+ this._createFaqsGroups();
18
+ this._createNavbar();
19
+ }
20
+
21
+ // =================
22
+ // == Private API ==
23
+ // =================
24
+
25
+ _createNavbar() {
26
+ const $faqsNavbar = $(".mu-faqs-navbar nav ul");
27
+ $('.mu-faqs-group').each((_index, faqGroup) => {
28
+ const $navItem = this._createNavbarItem($faqsNavbar, faqGroup)
29
+ const $faqGroup = $(faqGroup);
30
+ this._configureClickFor($navItem, $faqGroup, $faqsNavbar)
31
+ });
32
+ }
33
+
34
+ _createNavbarItem($faqsNavbar, faqGroup) {
35
+ const $newLink = $('<a></a>').attr("href",`#${faqGroup.id}`)
36
+ .html(`${faqGroup.children[0].textContent}`);
37
+ const $navItem = $('<li></li>').html($newLink);
38
+ $faqsNavbar.append($navItem);
39
+ return $navItem;
40
+ }
41
+
42
+ _configureClickFor($navItem, $faqGroup, $faqsNavbar) {
43
+ $navItem.click(function(e){
44
+ e.preventDefault();
45
+ $faqsNavbar.find('li').removeClass('active');
46
+ $navItem.addClass('active');
47
+ $('html, body').animate({scrollTop: $faqGroup.offset().top}, 1000);
48
+ });
49
+ }
50
+
51
+ _createFaqsGroups() {
52
+ const elemsGroups = this._buildFaqsGroups();
53
+ elemsGroups.forEach((group, index) => {
54
+ $(group).wrapAll(`<div class='mu-faqs-group' id='mu-faqs-group-${index}'>`)
55
+ });
56
+ }
57
+
58
+ _buildFaqsGroups() {
59
+ let newGroup = [];
60
+ let elemsGroups = [];
61
+ let previousNodeName;
62
+ $(".mu-faqs-content").children().each((index, elem) => {
63
+ if(elem.nodeName === this.topHierarchyElem && newGroup.length) {
64
+ elemsGroups.push(newGroup);
65
+ newGroup = [];
66
+ }
67
+ newGroup.push(elem);
68
+ previousNodeName = elem.nodeName
69
+ });
70
+
71
+ elemsGroups.push(newGroup);
72
+
73
+ return elemsGroups;
74
+ }
75
+
76
+ };
77
+
78
+ mumuki.load(() => {
79
+ new mumuki.faqs().load();
80
+ });
@@ -1,6 +1,7 @@
1
1
  @import "modules/avatar";
2
2
  @import "modules/book_header";
3
3
  @import "modules/breadcrumb";
4
+ @import "modules/certificate";
4
5
  @import "modules/checkboxes";
5
6
  @import "modules/console";
6
7
  @import "modules/content_show";
@@ -10,6 +11,7 @@
10
11
  @import "modules/editor";
11
12
  @import "modules/exercise_assignment";
12
13
  @import "modules/exercise_results";
14
+ @import "modules/faqs";
13
15
  @import "modules/flash";
14
16
  @import "modules/gs-board";
15
17
  @import "modules/guide_corollary";
@@ -0,0 +1,33 @@
1
+ .mu-certificate {
2
+ border: 1px solid $mu-color-dark-separator;
3
+ }
4
+
5
+ .mu-certificate-name {
6
+ margin-top: 0;
7
+ margin-bottom: 1em;
8
+ }
9
+
10
+ .mu-certificate-data {
11
+ margin-top: 30px;
12
+ }
13
+
14
+ .mu-certificate-buttons {
15
+ margin-bottom: 30px;
16
+ }
17
+
18
+ .mu-certificate-download-btn {
19
+ height: 35px;
20
+ padding: 0;
21
+ width: 157px;
22
+ border: none;
23
+ }
24
+ .mu-certificate-download-btn-icon {
25
+ border-right: 1px solid darken($mu-color-complementary, 10%);
26
+ padding: 8px 6px 9px 6px;
27
+ width: 35px;
28
+ }
29
+ .mu-certificate-download-btn-text {
30
+ display: inline-block;
31
+ width: 115px;
32
+ text-align: center;
33
+ }
@@ -0,0 +1,62 @@
1
+ .mu-faqs-container {
2
+ position: relative;
3
+ display: table;
4
+
5
+ .mu-faqs-content {
6
+ padding-top: 50px;
7
+
8
+ @media (min-width: $container-sm) {
9
+ width: 75%;
10
+ float: right;
11
+ }
12
+
13
+ .mu-faqs-group {
14
+ padding: 32px;
15
+ box-shadow: 0 0.375em 2.8125em 0 #d2d5d9;
16
+ margin-bottom: 32px;
17
+ h2 {
18
+ margin-top: 0;
19
+ margin-bottom: 20px;
20
+ }
21
+ h3 {
22
+ color: #1A94A3; // should be $brand-secondary
23
+ margin-top: 40px;
24
+ }
25
+ }
26
+ }
27
+
28
+ .mu-faqs-navbar {
29
+ top: 0;
30
+ position: sticky;
31
+ width: 25%;
32
+ padding-top: 50px;
33
+
34
+ @media (min-width: $container-sm) {
35
+ display: hidden;
36
+ }
37
+
38
+ ul {
39
+ border-left: 1px solid $mu-color-disabled;
40
+ list-style: none;
41
+ padding-left: 0px;
42
+ li {
43
+ padding: 8px 10px 8px 15px;
44
+ cursor: pointer;
45
+ &.active {
46
+ border-left: 3px solid transparent;
47
+ border-color: darken($brand-primary, 5%);
48
+ margin-left: -3px;
49
+ }
50
+ a {
51
+ color: $brand-primary;
52
+ &:hover {
53
+ text-decoration: none;
54
+ }
55
+ @media (min-width: $container-lg) {
56
+ font-size: 1.1em;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ class CertificatesController < ApplicationController
2
+ include WithCertificateRender
3
+
4
+ before_action :authorize_if_private!, only: [:show]
5
+ before_action :set_certificate!
6
+ before_action :validate_current_user!, only: [:download]
7
+
8
+ def verify
9
+ end
10
+
11
+ def show
12
+ end
13
+
14
+ def download
15
+ send_data pdf_for(@certificate), filename: @certificate.filename
16
+ end
17
+
18
+ private
19
+
20
+ def set_certificate!
21
+ @certificate = Certificate.find_by! code: params[:code]
22
+ end
23
+
24
+ def validate_current_user!
25
+ raise Mumuki::Domain::NotFoundError unless @certificate.for_user? current_user
26
+ end
27
+
28
+ end
@@ -0,0 +1,25 @@
1
+ require 'wicked_pdf'
2
+ require 'rqrcode'
3
+
4
+ module WithCertificateRender
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :qr_for
10
+ end
11
+
12
+ def qr_for(certificate)
13
+ qr = RQRCode::QRCode.new(verify_certificate_url certificate.code).as_svg(color: '0B465D')
14
+ "data:image/svg+xml,#{URI.encode(qr)}"
15
+ end
16
+
17
+ def pdf_for(certificate)
18
+ pdf_html = render_to_string(partial: 'certificates/download', locals: { certificate: certificate })
19
+ WickedPdf.new.pdf_from_string pdf_html,
20
+ orientation: 'Landscape',
21
+ page_size: 'A5',
22
+ margin: { top: 0.5, left: 1, bottom: 0.5, right: 1 }
23
+
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class FAQsController < ApplicationController
2
+ def index
3
+ @faqs = Organization.current.faqs_html
4
+ raise Mumuki::Domain::NotFoundError unless @faqs.present?
5
+ end
6
+ end
@@ -30,6 +30,10 @@ class UsersController < ApplicationController
30
30
  @watched_discussions = current_user.watched_discussions_in_organization
31
31
  end
32
32
 
33
+ def certificates
34
+ @certificates ||= current_user.certificates_in_organization
35
+ end
36
+
33
37
  def unsubscribe
34
38
  user_id = User.unsubscription_verifier.verify(params[:id])
35
39
  User.find(user_id).unsubscribe_from_reminders!
@@ -0,0 +1,13 @@
1
+ module CertificateHelper
2
+ def linkedin_post_url(certificate)
3
+ URI::HTTPS.build host: 'www.linkedin.com',
4
+ path: '/profile/add',
5
+ query: Rack::Utils.build_query(startTask: 'CERTIFICATION_NAME',
6
+ name: certificate.title,
7
+ organizationId: ENV['MUMUKI_LINKEDIN_ORGANIZATION_ID'],
8
+ issueYear: certificate.created_at.year,
9
+ issueMonth: certificate.created_at.month,
10
+ certUrl: verify_certificate_url(certificate.code),
11
+ certId: certificate.code)
12
+ end
13
+ end
@@ -59,10 +59,18 @@ module LinksHelper
59
59
  link_to t(:forum_terms), discussions_terms_path, target: '_blank'
60
60
  end
61
61
 
62
+ def link_to_faqs
63
+ link_to t(:faqs), faqs_path, target: '_blank' if faqs_enabled_here?
64
+ end
65
+
62
66
  def turbolinks_enable_for(exercise)
63
67
  %Q{data-turbolinks="#{!exercise.input_kids?}"}.html_safe
64
68
  end
65
69
 
70
+ def faqs_enabled_here?
71
+ Organization.current.faqs.present?
72
+ end
73
+
66
74
  private
67
75
 
68
76
  def extract_name(named, options)
@@ -1,9 +1,9 @@
1
1
  module MenuBarHelper
2
2
  def menu_bar_links
3
3
  [
4
- link_to_profile,
5
- link_to_classroom,
6
- link_to_bibliotheca,
4
+ menu_link_to_profile,
5
+ menu_link_to_classroom,
6
+ menu_link_to_bibliotheca,
7
7
  solve_discussions_link,
8
8
  user_discussions_link
9
9
  ]
@@ -17,25 +17,25 @@ module MenuBarHelper
17
17
  content_tag :li, link
18
18
  end
19
19
 
20
- def link_to_profile
20
+ def menu_link_to_profile
21
21
  menu_item('user', :my_account, user_path)
22
22
  end
23
23
 
24
- def link_to_classroom
25
- link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
24
+ def menu_link_to_classroom
25
+ menu_link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
26
26
  end
27
27
 
28
- def link_to_bibliotheca
29
- link_to_application :book, :bibliotheca_ui, :writer?
28
+ def menu_link_to_bibliotheca
29
+ menu_link_to_application :book, :bibliotheca_ui, :writer?
30
30
  end
31
31
 
32
- def link_to_application(icon, app_name, minimal_permissions)
32
+ def menu_link_to_application(icon, app_name, minimal_permissions)
33
33
  return unless current_user&.send(minimal_permissions)
34
34
  url = url_for_application(app_name)
35
35
  menu_item icon, app_name, url
36
36
  end
37
37
 
38
- def logout_link
38
+ def logout_menu_link
39
39
  li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for))
40
40
  end
41
41
 
@@ -46,4 +46,8 @@ module MenuBarHelper
46
46
  def any_menu_bar_links?
47
47
  menu_bar_links.any?
48
48
  end
49
+
50
+ def menu_link_to_faqs
51
+ li_tag menu_item('question', :faqs, faqs_path)
52
+ end
49
53
  end
@@ -11,6 +11,10 @@ module UserMenuHelper
11
11
  user_menu_link t(:discussions), discussions_user_path, 'discussions'
12
12
  end
13
13
 
14
+ def certificates_user_menu_link
15
+ user_menu_link t(:certificates), certificates_user_path, 'certificates'
16
+ end
17
+
14
18
  def user_menu_link(label, path, active_on)
15
19
  link_klass = 'active' if action_name == active_on
16
20
  link_to label, path, { class: link_klass }.compact
@@ -13,4 +13,3 @@ class ApplicationMailer < ActionMailer::Base
13
13
  mailer_environment_variables.all? { |env_var| ENV[env_var].present? }
14
14
  end
15
15
  end
16
-
@@ -1,4 +1,6 @@
1
1
  class UserMailer < ApplicationMailer
2
+ include WithCertificateRender
3
+
2
4
  def welcome_email(user, organization)
3
5
  with_locale(user, organization) do
4
6
  organization_name = organization.display_name || t(:your_new_organization)
@@ -18,6 +20,13 @@ class UserMailer < ApplicationMailer
18
20
  end
19
21
  end
20
22
 
23
+ def certificate(certificate)
24
+ with_locale certificate.user, certificate.organization do
25
+ attachments[certificate.filename] = pdf_for(certificate)
26
+ mail to: certificate.user.email, subject: t(:certificate)
27
+ end
28
+ end
29
+
21
30
  def with_locale(user, organization = nil, &block)
22
31
  @user = user
23
32
  @unsubscribe_code = User.unsubscription_verifier.generate(user.id)
@@ -0,0 +1,44 @@
1
+ <style>
2
+
3
+ .mu-certificate-box {
4
+ overflow: hidden;
5
+ height: 210mm;
6
+ width: 297mm;
7
+ max-height: 210mm;
8
+ max-width: 297mm;
9
+ min-height: 210mm;
10
+ min-width: 297mm;
11
+ margin: 0;
12
+ box-sizing: border-box;
13
+ position: relative;
14
+ border: 1px solid lightgrey;
15
+ }
16
+
17
+ .mu-certificate-box section {
18
+ background: transparent;
19
+ z-index: 10;
20
+ }
21
+
22
+ .background-image {
23
+ position: absolute;
24
+ height: 210mm;
25
+ width: 297mm;
26
+ margin: 0;
27
+ padding: 0;
28
+ z-index: 0;
29
+ }
30
+
31
+ .qr-code {
32
+ position: absolute;
33
+ z-index: 10;
34
+ }
35
+
36
+ </style>
37
+
38
+ <div class="mu-certificate-box">
39
+ <img src="<%= certificate.background_image_url %>" class="background-image"/>
40
+ <%= render inline: certificate.template_html_erb, locals: certificate.template_locals %>
41
+ <a href="<%= verify_certificate_path certificate.code %>" target="_blank">
42
+ <div><img class="qr-code" src="<%= qr_for certificate %>"/></div>
43
+ </a>
44
+ </div>