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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/mumuki_laboratory/application/certificate.js +17 -0
- data/app/assets/javascripts/mumuki_laboratory/application/faqs.js +80 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +2 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_certificate.scss +33 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +62 -0
- data/app/controllers/certificates_controller.rb +28 -0
- data/app/controllers/concerns/with_certificate_render.rb +25 -0
- data/app/controllers/faqs_controller.rb +6 -0
- data/app/controllers/users_controller.rb +4 -0
- data/app/helpers/certificate_helper.rb +13 -0
- data/app/helpers/links_helper.rb +8 -0
- data/app/helpers/menu_bar_helper.rb +14 -10
- data/app/helpers/user_menu_helper.rb +4 -0
- data/app/mailers/application_mailer.rb +0 -1
- data/app/mailers/user_mailer.rb +9 -0
- data/app/views/certificates/_certificate.html.erb +44 -0
- data/app/views/certificates/_download.html.erb +20 -0
- data/app/views/certificates/verify.html.erb +40 -0
- data/app/views/faqs/index.html.erb +16 -0
- data/app/views/layouts/_user_menu.html.erb +4 -0
- data/app/views/layouts/application.html.erb +6 -1
- data/app/views/layouts/exercise_inputs/editors/_code.html.erb +2 -1
- data/app/views/user_mailer/certificate.html.erb +339 -0
- data/app/views/user_mailer/certificate.text.erb +10 -0
- data/app/views/users/certificates.html.erb +32 -0
- data/config/routes.rb +6 -0
- data/lib/mumuki/laboratory/locales/en.yml +7 -0
- data/lib/mumuki/laboratory/locales/es-CL.yml +7 -0
- data/lib/mumuki/laboratory/locales/es.yml +7 -0
- data/lib/mumuki/laboratory/locales/pt.yml +7 -0
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/certificates_controller_spec.rb +15 -0
- data/spec/dummy/db/schema.rb +2 -1
- data/spec/features/certificate_programs_flow_spec.rb +17 -0
- data/spec/features/menu_bar_spec.rb +20 -0
- data/spec/features/profile_flow_spec.rb +12 -0
- data/spec/helpers/certificate_helper_spec.rb +15 -0
- metadata +67 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bfbac4c5667f84f0ef0365430b3b8b22bb421d0149a906bff69f7ad8116200a
|
4
|
+
data.tar.gz: b3a2468082c345db35724748e1e1d992e1a7fd0e4688a480040e873ac5d94650
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
@@ -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
|
data/app/helpers/links_helper.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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
|
20
|
+
def menu_link_to_profile
|
21
21
|
menu_item('user', :my_account, user_path)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def menu_link_to_classroom
|
25
|
+
menu_link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def menu_link_to_bibliotheca
|
29
|
+
menu_link_to_application :book, :bibliotheca_ui, :writer?
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
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
|
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
|
data/app/mailers/user_mailer.rb
CHANGED
@@ -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>
|