mumuki-laboratory 9.0.1 → 9.0.2

Sign up to get free protection for your applications and to get access to all the features.
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>