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.
- 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>
|