Merge pull request #15868 from edx/andya/profile-certificates
LEARNER-1860: Course certificates on the learner profile
3
.gitignore
vendored
@@ -107,8 +107,7 @@ cms/static/css/
|
||||
cms/static/sass/*.css
|
||||
cms/static/sass/*.css.map
|
||||
cms/static/themed_sass/
|
||||
themes/**/css/*.css
|
||||
themes/**/css/discussion/*.css
|
||||
themes/**/css
|
||||
|
||||
### Logging artifacts
|
||||
log/
|
||||
|
||||
@@ -245,6 +245,7 @@ def cert_info(user, course_overview, course_mode):
|
||||
"""
|
||||
if not course_overview.may_certify():
|
||||
return {}
|
||||
# Note: this should be rewritten to use the certificates API
|
||||
return _cert_info(
|
||||
user,
|
||||
course_overview,
|
||||
|
||||
BIN
lms/static/images/certificates/audit.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
lms/static/images/certificates/honor.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
lms/static/images/certificates/professional.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
lms/static/images/certificates/verified.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -7,3 +7,4 @@
|
||||
@import 'base/reset';
|
||||
@import 'base/variables';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
@import 'base/reset';
|
||||
@import 'base/variables';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// base - utilities
|
||||
@import 'base/variables';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
footer#footer-edx-v3 {
|
||||
@import 'base/extends';
|
||||
|
||||
@@ -1,10 +1,102 @@
|
||||
// lms - application - learner profile
|
||||
// ====================
|
||||
|
||||
// Table of Contents
|
||||
// * +Container - Learner Profile
|
||||
// * +Main - Header
|
||||
// * +Settings Section
|
||||
.learner-achievements {
|
||||
.learner-message {
|
||||
@extend %no-content;
|
||||
margin: $baseline*0.75 0;
|
||||
|
||||
.message-header, .message-actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message-actions {
|
||||
margin-top: $baseline/2;
|
||||
|
||||
.btn-brand {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: $baseline;
|
||||
padding: $baseline/2;
|
||||
border: 1px;
|
||||
border-style: solid;
|
||||
background-color: $white;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 1px 1px $gray-l2;
|
||||
}
|
||||
|
||||
.card-logo {
|
||||
@include margin-right($baseline);
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: $base-font-color;
|
||||
margin-top: $baseline/2;
|
||||
}
|
||||
|
||||
.card-supertitle {
|
||||
@extend %t-title6;
|
||||
color: $lightest-base-font-color;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
@extend %t-title5;
|
||||
@extend %t-strong;
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
@extend %t-title8;
|
||||
color: $lightest-base-font-color;
|
||||
}
|
||||
|
||||
&.mode-audit {
|
||||
border-color: $audit-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/audit.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-honor {
|
||||
border-color: $honor-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/honor.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-verified {
|
||||
border-color: $verified-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/verified.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-professional {
|
||||
border-color: $professional-certificate-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/professional.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-profile {
|
||||
$profile-image-dimension: 120px;
|
||||
@@ -210,133 +302,146 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-section-one {
|
||||
@include float(left);
|
||||
@include margin-left($baseline*3);
|
||||
width: 300px;
|
||||
background-color: $white;
|
||||
border-top: 5px solid $blue;
|
||||
padding-bottom: $baseline;
|
||||
|
||||
.wrapper-profile-section-container-one {
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include margin-left(0);
|
||||
width: 100%;
|
||||
width: 90%;
|
||||
padding: 0 5%;
|
||||
}
|
||||
|
||||
.wrapper-profile-section-one {
|
||||
@include float(left);
|
||||
@include margin-left($baseline*3);
|
||||
width: 300px;
|
||||
background-color: $white;
|
||||
border-top: 5px solid $blue;
|
||||
padding-bottom: $baseline;
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include margin-left(0);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-section-one-fields {
|
||||
margin: 0 $baseline/2;
|
||||
|
||||
.social-links {
|
||||
font-size: 2rem;
|
||||
padding-top: $baseline/4;
|
||||
|
||||
& > span {
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
a {
|
||||
.fa-facebook-square {
|
||||
color: $facebook-blue;
|
||||
}
|
||||
|
||||
.fa-twitter-square {
|
||||
color: $twitter-blue;
|
||||
}
|
||||
|
||||
.fa-linkedin-square {
|
||||
color: $linkedin-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-field {
|
||||
@extend %t-weight4;
|
||||
@include padding(0, 0, 0, 3px);
|
||||
color: $base-font-color;
|
||||
margin-top: $baseline/5;
|
||||
|
||||
.u-field-value, .u-field-title {
|
||||
@extend %t-weight4;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.u-field-value-readonly {
|
||||
@extend %t-weight3;
|
||||
font-family: $sans-serif;
|
||||
color: $darkest-base-font-color;
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
color: $lightest-base-font-color;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.u-field-dropdown {
|
||||
position: relative;
|
||||
|
||||
&:not(.editable-never) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: $baseline/4;
|
||||
border-bottom: 1px solid $gray-lighter;
|
||||
|
||||
&:hover.mode-placeholder {
|
||||
padding-bottom: $baseline/5;
|
||||
border-bottom: 2px dashed $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>.u-field {
|
||||
&:not(:first-child) {
|
||||
font-size: $body-font-size;
|
||||
color: $base-font-color;
|
||||
font-weight: $font-light;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@extend %t-title4;
|
||||
@extend %t-weight4;
|
||||
font-size: em(24);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
width: 85%
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@include right(0);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 20px;
|
||||
|
||||
.icon {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-section-one-fields {
|
||||
@include margin(0, $baseline/2, 0, $baseline*0.75);
|
||||
|
||||
.social-links {
|
||||
font-size: 2rem;
|
||||
padding-top: $baseline/4;
|
||||
|
||||
& > span {
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
a {
|
||||
.fa-facebook-square {
|
||||
color: $facebook-blue;
|
||||
}
|
||||
|
||||
.fa-twitter-square {
|
||||
color: $twitter-blue;
|
||||
}
|
||||
|
||||
.fa-linkedin-square {
|
||||
color: $linkedin-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-field {
|
||||
@extend %t-weight4;
|
||||
padding: 0;
|
||||
color: $base-font-color;
|
||||
margin-top: $baseline/5;
|
||||
|
||||
.u-field-value, .u-field-title {
|
||||
@extend %t-weight4;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.u-field-value-readonly {
|
||||
font-weight: 500;
|
||||
font-family: $sans-serif;
|
||||
color: $darkest-base-font-color;
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
color: $lightest-base-font-color;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:not(.u-field-readonly):not(:last-child) {
|
||||
padding-bottom: $baseline/4;
|
||||
border-bottom: 1px solid $gray-lighter;
|
||||
|
||||
&:hover.mode-placeholder {
|
||||
padding-bottom: $baseline/5;
|
||||
border-bottom: 2px dashed $link-color;
|
||||
}
|
||||
}
|
||||
&.u-field-dropdown {
|
||||
position: relative;
|
||||
|
||||
&:not(.editable-never) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&>.u-field {
|
||||
&:not(:first-child) {
|
||||
font-size: $body-font-size;
|
||||
color: $base-font-color;
|
||||
font-weight: $font-light;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@extend %t-title4;
|
||||
@extend %t-weight4;
|
||||
font-size: em(24);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
width: 85%
|
||||
}
|
||||
|
||||
.u-field-message {
|
||||
@include right(0);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 20px;
|
||||
|
||||
.icon {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-section-container-two {
|
||||
@include float(left);
|
||||
@include padding-left($baseline);
|
||||
width: calc(100% - 380px);
|
||||
max-width: $learner-profile-container-flex; // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
font-family: $sans-serif;
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include padding-left(0);
|
||||
width: 100%;
|
||||
width: 90%;
|
||||
margin-top: $baseline;
|
||||
padding: 0 5%;
|
||||
}
|
||||
|
||||
.u-field-textarea {
|
||||
@include padding(0, ($baseline*.75), ($baseline*.75), 0);
|
||||
margin-bottom: ($baseline/2);
|
||||
@include padding(0, ($baseline*.75), ($baseline*.75), ($baseline/4));
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include padding-left($baseline/4);
|
||||
}
|
||||
|
||||
.u-field-header {
|
||||
position: relative;
|
||||
@@ -355,12 +460,11 @@
|
||||
|
||||
.u-field-title {
|
||||
@extend %t-title6;
|
||||
@extend %t-weight5;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
margin-bottom: ($baseline/4);
|
||||
color: $gray-dark;
|
||||
width: 100%;
|
||||
font: $font-semibold 1.4em/1.4em $sans-serif;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
@@ -396,7 +500,7 @@
|
||||
|
||||
.u-field.mode-placeholder {
|
||||
padding: $baseline;
|
||||
margin: $baseline * 0.75;
|
||||
margin: $baseline*0.75 0;
|
||||
border: 2px dashed $gray-l3;
|
||||
|
||||
i {
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
@import 'base/variables';
|
||||
@import 'base/font_face';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
@import 'build-course'; // shared app style assets/rendering
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
@import 'base/variables';
|
||||
@import 'base/font_face';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
@import 'build-course'; // shared app style assets/rendering
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
@import 'base/variables-rtl';
|
||||
|
||||
// Import shared build for the edx.org footer
|
||||
@import 'build-footer-edx'
|
||||
@import 'build-footer-edx';
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
@import 'base/variables-ltr';
|
||||
|
||||
// Import shared build for the edx.org footer
|
||||
@import 'build-footer-edx'
|
||||
@import 'build-footer-edx';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// base - utilities
|
||||
@import 'base/variables';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
footer#footer-openedx {
|
||||
@import 'base/reset';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// base - utilities
|
||||
@import 'base/variables';
|
||||
@import 'base/mixins';
|
||||
@import 'base/theme';
|
||||
|
||||
footer#footer-openedx {
|
||||
@import 'base/reset';
|
||||
|
||||
1
lms/static/sass/partials/base/_theme.scss
Normal file
@@ -0,0 +1 @@
|
||||
// File to be overridden by themes
|
||||
@@ -248,11 +248,14 @@ $state-danger-border: darken($state-danger-bg, 5%) !default;
|
||||
// ----------------------------
|
||||
|
||||
// logo colors
|
||||
$micromasters-color: #005585;
|
||||
$xseries-color: #424242;
|
||||
$professional-certificate-color: #9a1f60;
|
||||
$zebra-stripe-color: rgb(249, 250, 252);
|
||||
$divider-color: rgb(226,231,236);
|
||||
$audit-mode-color: $gray-dark !default;
|
||||
$honor-mode-color: $uxpl-blue-base !default;
|
||||
$verified-mode-color: $uxpl-green-base !default;
|
||||
$micromasters-color: #005585 !default;
|
||||
$xseries-color: #424242 !default;
|
||||
$professional-certificate-color: #9a1f60 !default;
|
||||
$zebra-stripe-color: rgb(249, 250, 252) !default;
|
||||
$divider-color: rgb(226,231,236) !default;
|
||||
|
||||
// old color variables
|
||||
// DEPRECATED: Do not continue to use these colors, instead use pattern libary and base colors above.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Learner profile settings and helper methods.
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
|
||||
|
||||
|
||||
# Namespace for learner profile waffle flags.
|
||||
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='learner_profile')
|
||||
|
||||
# Waffle flag to show achievements on the learner profile.
|
||||
# TODO: LEARNER-2443: 08/2017: Remove flag after rollout.
|
||||
SHOW_ACHIEVEMENTS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_achievements')
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
<div class="message-banner" aria-live="polite"></div>
|
||||
<div class="wrapper-profile">
|
||||
<div class="ui-loading-indicator">
|
||||
<p>
|
||||
<div class="profile profile-other">
|
||||
<div class="wrapper-profile-field-account-privacy"></div>
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
<div class="wrapper-profile-section-container-one">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-image-field">
|
||||
</div>
|
||||
<div class="profile-section-one-fields">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy">An error occurred. Try loading the page again.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-profile-section-container-two">
|
||||
<div class="wrapper-profile-bio">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-indicator">
|
||||
<p>
|
||||
<span class="spin">
|
||||
<span class="icon fa fa-refresh" aria-hidden="true"></span>
|
||||
</span>
|
||||
<span class="copy">
|
||||
<span class="copy">
|
||||
Loading
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy">
|
||||
</p>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy">
|
||||
An error occurred. Please reload the page.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,14 +36,14 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
};
|
||||
|
||||
var expectProfilePrivacyFieldTobeRendered = function(learnerProfileView, othersProfile) {
|
||||
var accountPrivacyElement = learnerProfileView.$('.wrapper-profile-field-account-privacy');
|
||||
var privacyFieldElement = $(accountPrivacyElement).find('.u-field');
|
||||
var $accountPrivacyElement = $('.wrapper-profile-field-account-privacy');
|
||||
var $privacyFieldElement = $($accountPrivacyElement).find('.u-field');
|
||||
|
||||
if (othersProfile) {
|
||||
expect(privacyFieldElement.length).toBe(0);
|
||||
expect($privacyFieldElement.length).toBe(0);
|
||||
} else {
|
||||
expect(privacyFieldElement.length).toBe(1);
|
||||
expectProfileElementContainsField(privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView);
|
||||
expect($privacyFieldElement.length).toBe(1);
|
||||
expectProfileElementContainsField($privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -65,12 +65,12 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
};
|
||||
|
||||
var expectSectionTwoTobeRendered = function(learnerProfileView) {
|
||||
var sectionTwoElement = learnerProfileView.$('.wrapper-profile-section-two');
|
||||
var sectionTwoFieldElements = $(sectionTwoElement).find('.u-field');
|
||||
var $sectionTwoElement = $('.wrapper-profile-section-two');
|
||||
var $sectionTwoFieldElements = $($sectionTwoElement).find('.u-field');
|
||||
|
||||
expect(sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
|
||||
expect($sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
|
||||
|
||||
_.each(sectionTwoFieldElements, function(sectionFieldElement, fieldIndex) {
|
||||
_.each($sectionTwoFieldElements, function(sectionFieldElement, fieldIndex) {
|
||||
expectProfileElementContainsField(
|
||||
sectionFieldElement,
|
||||
learnerProfileView.options.sectionTwoFieldViews[fieldIndex]
|
||||
@@ -85,7 +85,7 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
};
|
||||
|
||||
var expectLimitedProfileSectionsAndFieldsToBeRendered = function(learnerProfileView, othersProfile) {
|
||||
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
|
||||
var sectionOneFieldElements = $('.wrapper-profile-section-one').find('.u-field');
|
||||
|
||||
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
|
||||
|
||||
@@ -108,9 +108,9 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
};
|
||||
|
||||
var expectProfileSectionsNotToBeRendered = function(learnerProfileView) {
|
||||
expect(learnerProfileView.$('.wrapper-profile-field-account-privacy').length).toBe(0);
|
||||
expect(learnerProfileView.$('.wrapper-profile-section-one').length).toBe(0);
|
||||
expect(learnerProfileView.$('.wrapper-profile-section-two').length).toBe(0);
|
||||
expect($('.wrapper-profile-field-account-privacy').length).toBe(0);
|
||||
expect($('.wrapper-profile-section-one').length).toBe(0);
|
||||
expect($('.wrapper-profile-section-two').length).toBe(0);
|
||||
};
|
||||
|
||||
var expectTabbedViewToBeUndefined = function(requests, tabbedViewView) {
|
||||
@@ -124,42 +124,42 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
};
|
||||
|
||||
var expectBadgesDisplayed = function(learnerProfileView, length, lastPage) {
|
||||
var badgeListingView = learnerProfileView.$el.find('#tabpanel-accomplishments'),
|
||||
var $badgeListingView = $('#tabpanel-accomplishments'),
|
||||
updatedLength = length,
|
||||
placeholder;
|
||||
expect(learnerProfileView.$el.find('#tabpanel-about_me').hasClass('is-hidden')).toBe(true);
|
||||
expect(badgeListingView.hasClass('is-hidden')).toBe(false);
|
||||
expect($('#tabpanel-about_me').hasClass('is-hidden')).toBe(true);
|
||||
expect($badgeListingView.hasClass('is-hidden')).toBe(false);
|
||||
if (lastPage) {
|
||||
updatedLength += 1;
|
||||
placeholder = badgeListingView.find('.find-course');
|
||||
placeholder = $badgeListingView.find('.find-course');
|
||||
expect(placeholder.length).toBe(1);
|
||||
expect(placeholder.attr('href')).toBe('/courses/');
|
||||
}
|
||||
expect(badgeListingView.find('.badge-display').length).toBe(updatedLength);
|
||||
expect($badgeListingView.find('.badge-display').length).toBe(updatedLength);
|
||||
};
|
||||
|
||||
var expectBadgesHidden = function(learnerProfileView) {
|
||||
var accomplishmentsTab = learnerProfileView.$el.find('#tabpanel-accomplishments');
|
||||
if (accomplishmentsTab.length) {
|
||||
var $accomplishmentsTab = $('#tabpanel-accomplishments');
|
||||
if ($accomplishmentsTab.length) {
|
||||
// Nonexistence counts as hidden.
|
||||
expect(learnerProfileView.$el.find('#tabpanel-accomplishments').hasClass('is-hidden')).toBe(true);
|
||||
expect($('#tabpanel-accomplishments').hasClass('is-hidden')).toBe(true);
|
||||
}
|
||||
expect(learnerProfileView.$el.find('#tabpanel-about_me').hasClass('is-hidden')).toBe(false);
|
||||
expect($('#tabpanel-about_me').hasClass('is-hidden')).toBe(false);
|
||||
};
|
||||
|
||||
var expectPage = function(learnerProfileView, pageData) {
|
||||
var badgeListContainer = learnerProfileView.$el.find('#tabpanel-accomplishments');
|
||||
var index = badgeListContainer.find('span.search-count').text().trim();
|
||||
var $badgeListContainer = $('#tabpanel-accomplishments');
|
||||
var index = $badgeListContainer.find('span.search-count').text().trim();
|
||||
expect(index).toBe('Showing ' + (pageData.start + 1) + '-' + (pageData.start + pageData.results.length) +
|
||||
' out of ' + pageData.count + ' total');
|
||||
expect(badgeListContainer.find('.current-page').text()).toBe('' + pageData.current_page);
|
||||
expect($badgeListContainer.find('.current-page').text()).toBe('' + pageData.current_page);
|
||||
_.each(pageData.results, function(badge) {
|
||||
expect($('.badge-display:contains(' + badge.badge_class.display_name + ')').length).toBe(1);
|
||||
});
|
||||
};
|
||||
|
||||
var expectBadgeLoadingErrorIsRendered = function(learnerProfileView) {
|
||||
var errorMessage = learnerProfileView.$el.find('.badge-set-display').text();
|
||||
var errorMessage = $('.badge-set-display').text();
|
||||
expect(errorMessage).toBe(
|
||||
'Your request could not be completed. Reload the page and try again. If the issue persists, click the ' +
|
||||
'Help tab to report the problem.'
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
[
|
||||
'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils',
|
||||
'common/js/components/views/tabbed_view',
|
||||
'learner_profile/js/views/section_two_tab',
|
||||
'text!learner_profile/templates/learner_profile.underscore',
|
||||
'edx-ui-toolkit/js/utils/string-utils'
|
||||
'learner_profile/js/views/section_two_tab'
|
||||
],
|
||||
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate, StringUtils) {
|
||||
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab) {
|
||||
var LearnerProfileView = Backbone.View.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
@@ -25,8 +23,6 @@
|
||||
this.firstRender = true;
|
||||
},
|
||||
|
||||
template: _.template(learnerProfileTemplate),
|
||||
|
||||
showFullProfile: function() {
|
||||
var isAboveMinimumAge = this.options.accountSettingsModel.isAboveMinimumAge();
|
||||
if (this.options.ownProfile) {
|
||||
@@ -54,22 +50,13 @@
|
||||
ownProfile: this.options.ownProfile
|
||||
});
|
||||
|
||||
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(learnerProfileTemplate)({
|
||||
username: self.options.accountSettingsModel.get('username'),
|
||||
name: self.options.accountSettingsModel.get('name'),
|
||||
ownProfile: self.options.ownProfile,
|
||||
showFullProfile: self.showFullProfile(),
|
||||
profile_header: gettext('My Profile'),
|
||||
profile_subheader:
|
||||
StringUtils.interpolate(
|
||||
gettext('Build out your profile to personalize your identity on {platform_name}.'), {
|
||||
platform_name: self.options.platformName
|
||||
}
|
||||
)
|
||||
}));
|
||||
this.renderFields();
|
||||
|
||||
// Reveal the profile and hide the loading indicator
|
||||
$('.ui-loading-indicator').addClass('is-hidden');
|
||||
$('.wrapper-profile-section-container-one').removeClass('is-hidden');
|
||||
$('.wrapper-profile-section-container-two').removeClass('is-hidden');
|
||||
|
||||
if (this.showFullProfile() && (this.options.accountSettingsModel.get('accomplishments_shared'))) {
|
||||
tabs = [
|
||||
{view: this.sectionTwoView, title: gettext('About Me'), url: 'about_me'},
|
||||
@@ -108,7 +95,8 @@
|
||||
Backbone.history.start();
|
||||
}
|
||||
} else {
|
||||
this.$el.find('.wrapper-profile-section-container-two').append(this.sectionTwoView.render().el);
|
||||
// xss-lint: disable=javascript-jquery-html
|
||||
this.$el.find('.wrapper-profile-bio').html(this.sectionTwoView.render().el);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>">
|
||||
<div class="wrapper-profile-field-account-privacy"></div>
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
<% if (ownProfile) { %>
|
||||
<div class="profile-header">
|
||||
<div class="header"> <%- profile_header %></div>
|
||||
<div class="subheader"> <%- profile_subheader %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="wrapper-profile-section-container-one">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-image-field">
|
||||
</div>
|
||||
<div class="profile-section-one-fields">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-profile-section-container-two">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,88 @@
|
||||
## mako
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<div class="learner-achievements">
|
||||
% if course_certificates or own_profile:
|
||||
<h3 class="u-field-title">Course Certificates</h3>
|
||||
% if course_certificates:
|
||||
% for certificate in course_certificates:
|
||||
<%
|
||||
certificate_url = certificate['download_url']
|
||||
course = certificate['course']
|
||||
|
||||
completion_date_message_html = Text(_('Completed {completion_date_html}')).format(
|
||||
completion_date=HTML(
|
||||
'<span'
|
||||
' class="localized-datetime start-date"'
|
||||
' data-datetime="{completion_date_html}"'
|
||||
' data-format="shortDate"'
|
||||
' data-timezone="{user_timezone}"'
|
||||
' data-language="{user_language}"'
|
||||
'></span>'
|
||||
).format(
|
||||
completion_date=certificate['created'],
|
||||
user_timezone=user_timezone,
|
||||
user_language=user_language,
|
||||
),
|
||||
)
|
||||
%>
|
||||
% if certificate_url:
|
||||
<a href="${certificate_url}" target="_blank">
|
||||
<div class="card certificate-card mode-${certificate['type']}">
|
||||
<div class="card-logo">
|
||||
<h4 class="sr-only">
|
||||
${_('{course_mode} certificate').format(
|
||||
course_mode=certificate['type'],
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-supertitle">${course.display_org_with_default}</div>
|
||||
<div class="card-title">${course.display_name_with_default}</div>
|
||||
<p class="card-text">${completion_date_message_html}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
% else:
|
||||
<div class="card certificate-card mode-${certificate['type']}">
|
||||
<div class="card-logo">
|
||||
<h4 class="sr-only">
|
||||
${_('{course_mode} certificate').format(
|
||||
course_mode=certificate['type'],
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-supertitle">${course.display_org_with_default}</div>
|
||||
<div class="card-title">${course.display_name_with_default}</div>
|
||||
<p class="card-text">${completion_date_message_html}</p>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
% elif own_profile:
|
||||
<div class="learner-message">
|
||||
<h4 class="message-header">${_("You haven't earned any certificates yet.")}</h4>
|
||||
<p class="message-actions">
|
||||
<a class="btn btn-brand" href="${marketing_link('COURSES')}">
|
||||
<span class="icon fa fa-search" aria-hidden="true"></span>
|
||||
${_('Explore New Courses')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
|
||||
DateUtilFactory.transform('.localized-datetime');
|
||||
</%static:require_module_async>
|
||||
|
||||
@@ -4,28 +4,65 @@
|
||||
<%inherit file="/main.html" />
|
||||
<%def name="online_help_token()"><% return "profile" %></%def>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
|
||||
<%!
|
||||
import json
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
%>
|
||||
|
||||
<%block name="pagetitle">${_("Learner Profile")}</%block>
|
||||
|
||||
<%block name="bodyclass">view-profile</%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course'/>
|
||||
</%block>
|
||||
|
||||
<div class="message-banner" aria-live="polite"></div>
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<div class="wrapper-profile">
|
||||
<div class="ui-loading-indicator">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
<div class="profile ${'profile-self' if own_profile else 'profile-other'}">
|
||||
<div class="wrapper-profile-field-account-privacy"></div>
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
% if own_profile:
|
||||
<div class="profile-header">
|
||||
<div class="header">${_("My Profile")}</div>
|
||||
<div class="subheader">
|
||||
${_('Build out your profile to personalize your identity on {platform_name}.').format(
|
||||
platform_name=platform_name,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="ui-loading-indicator">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
<div class="wrapper-profile-section-container-one is-hidden">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-image-field">
|
||||
</div>
|
||||
<div class="profile-section-one-fields">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy">${_("An error occurred. Try loading the page again.")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-profile-section-container-two is-hidden">
|
||||
% if achievements_fragment:
|
||||
${HTML(achievements_fragment.body_html())}
|
||||
% endif
|
||||
<div class="wrapper-profile-bio">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course'/>
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="learner_profile/js/learner_profile_factory" class_name="LearnerProfileFactory">
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Tests for student profile views. """
|
||||
|
||||
import datetime
|
||||
import ddt
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from student.tests.factories import UserFactory
|
||||
from util.testing import UrlResetMixin
|
||||
|
||||
from ..views import learner_profile_context
|
||||
from course_modes.models import CourseMode
|
||||
|
||||
from certificates.tests.factories import GeneratedCertificateFactory # pylint: disable=import-error
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
|
||||
from openedx.features.learner_profile.views.learner_profile import learner_profile_context
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
class LearnerProfileViewTest(UrlResetMixin, TestCase):
|
||||
@ddt.ddt
|
||||
class LearnerProfileViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
""" Tests for the student profile view. """
|
||||
|
||||
USERNAME = "username"
|
||||
OTHER_USERNAME = "other_user"
|
||||
PASSWORD = "password"
|
||||
DOWNLOAD_URL = "http://www.example.com/certificate.pdf"
|
||||
CONTEXT_DATA = [
|
||||
'default_public_account_fields',
|
||||
'accounts_api_url',
|
||||
@@ -32,7 +43,13 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase):
|
||||
def setUp(self):
|
||||
super(LearnerProfileViewTest, self).setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.other_user = UserFactory.create(username=self.OTHER_USERNAME, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.course = CourseFactory.create(
|
||||
start=datetime.datetime(2013, 9, 16, 7, 17, 28),
|
||||
end=datetime.datetime.now(),
|
||||
certificate_available_date=datetime.datetime.now(),
|
||||
)
|
||||
|
||||
def test_context(self):
|
||||
"""
|
||||
@@ -100,3 +117,48 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase):
|
||||
profile_path = reverse('learner_profile', kwargs={'username': "no_such_user"})
|
||||
response = self.client.get(path=profile_path)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
def _create_certificate(self, enrollment_mode):
|
||||
"""Simulate that the user has a generated certificate. """
|
||||
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, mode=enrollment_mode)
|
||||
return GeneratedCertificateFactory(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode=enrollment_mode,
|
||||
download_url=self.DOWNLOAD_URL,
|
||||
status="downloadable"
|
||||
)
|
||||
|
||||
@ddt.data(CourseMode.HONOR, CourseMode.PROFESSIONAL, CourseMode.VERIFIED)
|
||||
def test_certificate_visibility(self, cert_mode):
|
||||
"""
|
||||
Verify that certificates are displayed with the correct card mode.
|
||||
"""
|
||||
# Add new certificate
|
||||
cert = self._create_certificate(cert_mode)
|
||||
cert.save()
|
||||
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = self.user
|
||||
context = learner_profile_context(request, self.user.username, self.user.is_staff)
|
||||
|
||||
self.assertTrue('card certificate-card mode-' + cert_mode in str(context['achievements_fragment'].content))
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_no_certificate_visibility(self, own_profile):
|
||||
"""
|
||||
Verify that the 'You haven't earned any certificates yet.' well appears on the user's
|
||||
own profile when they do not have certificates and does not appear when viewing
|
||||
another user that does not have any certificates.
|
||||
"""
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = self.user
|
||||
profile_username = self.user.username if own_profile else self.other_user.username
|
||||
context = learner_profile_context(request, profile_username, self.user.is_staff)
|
||||
|
||||
if own_profile:
|
||||
content = str(context['achievements_fragment'].content)
|
||||
self.assertIn('icon fa fa-search', content)
|
||||
self.assertIn("You haven't earned any certificates yet", content)
|
||||
else:
|
||||
self.assertIsNone(context['achievements_fragment'])
|
||||
@@ -5,12 +5,19 @@ Defines URLs for the learner profile.
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from views.learner_achievements import LearnerAchievementsFragmentView
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^{username_pattern}$'.format(
|
||||
username_pattern=settings.USERNAME_PATTERN,
|
||||
),
|
||||
'openedx.features.learner_profile.views.learner_profile',
|
||||
'openedx.features.learner_profile.views.learner_profile.learner_profile',
|
||||
name='learner_profile',
|
||||
),
|
||||
url(
|
||||
r'^achievements$',
|
||||
LearnerAchievementsFragmentView.as_view(),
|
||||
name='openedx.learner_profile.learner_achievements_fragment_view',
|
||||
),
|
||||
]
|
||||
|
||||
0
openedx/features/learner_profile/views/__init__.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Views to render a learner's achievements.
|
||||
"""
|
||||
|
||||
from courseware.courses import get_course_overview_with_access
|
||||
from django.template.loader import render_to_string
|
||||
from lms.djangoapps.certificates import api as certificate_api
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
|
||||
class LearnerAchievementsFragmentView(EdxFragmentView):
|
||||
"""
|
||||
A fragment to render a learner's achievements.
|
||||
"""
|
||||
def render_to_fragment(self, request, username=None, own_profile=False, **kwargs):
|
||||
"""
|
||||
Renders the current learner's achievements.
|
||||
"""
|
||||
course_certificates = self._get_ordered_certificates_for_user(request, username)
|
||||
context = {
|
||||
'course_certificates': course_certificates,
|
||||
'own_profile': own_profile,
|
||||
'disable_courseware_js': True,
|
||||
}
|
||||
if course_certificates or own_profile:
|
||||
html = render_to_string('learner_profile/learner-achievements-fragment.html', context)
|
||||
return Fragment(html)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_ordered_certificates_for_user(self, request, username):
|
||||
"""
|
||||
Returns a user's certificates sorted by course name.
|
||||
"""
|
||||
course_certificates = certificate_api.get_certificates_for_user(username)
|
||||
for course_certificate in course_certificates:
|
||||
course_key = course_certificate['course_key']
|
||||
course_overview = get_course_overview_with_access(request.user, 'load', course_key)
|
||||
course_certificate['course'] = course_overview
|
||||
course_certificates.sort(key=lambda certificate: certificate['course'].display_name_with_default)
|
||||
return course_certificates
|
||||
@@ -17,6 +17,10 @@ from openedx.core.djangoapps.user_api.errors import UserNotAuthorized, UserNotFo
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
|
||||
from student.models import User
|
||||
|
||||
from .. import SHOW_ACHIEVEMENTS_FLAG
|
||||
|
||||
from learner_achievements import LearnerAchievementsFragmentView
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['GET'])
|
||||
@@ -70,7 +74,19 @@ def learner_profile_context(request, profile_username, user_is_staff):
|
||||
|
||||
preferences_data = get_user_preferences(profile_user, profile_username)
|
||||
|
||||
if SHOW_ACHIEVEMENTS_FLAG.is_enabled():
|
||||
achievements_fragment = LearnerAchievementsFragmentView().render_to_fragment(
|
||||
request,
|
||||
username=profile_user.username,
|
||||
own_profile=own_profile,
|
||||
)
|
||||
else:
|
||||
achievements_fragment = None
|
||||
|
||||
context = {
|
||||
'own_profile': own_profile,
|
||||
'achievements_fragment': achievements_fragment,
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
'data': {
|
||||
'profile_user_id': profile_user.id,
|
||||
'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'],
|
||||
BIN
themes/edge.edx.org/lms/static/images/certificates/honor.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
BIN
themes/edge.edx.org/lms/static/images/certificates/verified.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,30 @@
|
||||
// Certificate overrides for edge.edx.org
|
||||
|
||||
.certificate-card {
|
||||
// Note: edx.org no longer supports audit certificates, but there are
|
||||
// legacy certificates that might be rendered. In this situation, they
|
||||
// are styled as honor certificates.
|
||||
&.mode-honor, &.mode-audit {
|
||||
border-color: $honor-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/honor.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-verified {
|
||||
border-color: $verified-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/verified.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-professional {
|
||||
border-color: $professional-certificate-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/professional.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Theme overrides for edge.edx.org
|
||||
|
||||
@import 'base/certificates';
|
||||
BIN
themes/edx.org/lms/static/images/certificates/honor.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
themes/edx.org/lms/static/images/certificates/professional.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
themes/edx.org/lms/static/images/certificates/verified.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,30 @@
|
||||
// Certificate overrides for edx.org
|
||||
|
||||
.certificate-card {
|
||||
// Note: edx.org no longer supports audit certificates, but there are
|
||||
// legacy certificates that might be rendered. In this situation, they
|
||||
// are styled as honor certificates.
|
||||
&.mode-honor, &.mode-audit {
|
||||
border-color: $honor-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/honor.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-verified {
|
||||
border-color: $verified-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/verified.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-professional {
|
||||
border-color: $professional-certificate-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/professional.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
3
themes/edx.org/lms/static/sass/partials/base/_theme.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Theme overrides for edx.org
|
||||
|
||||
@import 'base/certificates';
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,35 @@
|
||||
// Certificate overrides for the red theme
|
||||
|
||||
.certificate-card {
|
||||
&.mode-audit {
|
||||
border-color: $audit-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-honor {
|
||||
border-color: $honor-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-verified {
|
||||
border-color: $verified-mode-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-professional {
|
||||
border-color: $professional-certificate-color;
|
||||
|
||||
.card-logo {
|
||||
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Theme overrides for the red theme
|
||||
|
||||
@import 'base/certificates';
|
||||