Commit 92c94b53 by Diana Huang

Merge pull request #1483 from edx/diana/certs-upgrade-path

Verified Certificate Upsell
parents de7bba51 a9da48ac
......@@ -9,7 +9,7 @@ from collections import namedtuple
from django.utils.translation import ugettext as _
from django.db.models import Q
Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency'])
Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency', 'expiration_date'])
class CourseMode(models.Model):
......@@ -39,7 +39,7 @@ class CourseMode(models.Model):
# turn this mode off after the given expiration date
expiration_date = models.DateField(default=None, null=True, blank=True)
DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd')
DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd', None)
DEFAULT_MODE_SLUG = 'honor'
class Meta:
......@@ -57,8 +57,14 @@ class CourseMode(models.Model):
found_course_modes = cls.objects.filter(Q(course_id=course_id) &
(Q(expiration_date__isnull=True) |
Q(expiration_date__gte=now)))
modes = ([Mode(mode.mode_slug, mode.mode_display_name, mode.min_price, mode.suggested_prices, mode.currency)
for mode in found_course_modes])
modes = ([Mode(
mode.mode_slug,
mode.mode_display_name,
mode.min_price,
mode.suggested_prices,
mode.currency,
mode.expiration_date
) for mode in found_course_modes])
if not modes:
modes = [cls.DEFAULT_MODE]
return modes
......
......@@ -49,7 +49,7 @@ class CourseModeModelTest(TestCase):
self.create_mode('verified', 'Verified Certificate')
modes = CourseMode.modes_for_course(self.course_id)
mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None)
self.assertEqual([mode], modes)
modes_dict = CourseMode.modes_for_course_dict(self.course_id)
......@@ -61,8 +61,8 @@ class CourseModeModelTest(TestCase):
"""
Finding the modes when there's multiple modes
"""
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd')
mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None)
mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None)
set_modes = [mode1, mode2]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices)
......@@ -81,9 +81,9 @@ class CourseModeModelTest(TestCase):
self.assertEqual(0, CourseMode.min_course_price_for_currency(self.course_id, 'usd'))
# create some modes
mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd')
mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd')
mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny')
mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd', None)
mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd', None)
mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny', None)
set_modes = [mode1, mode2, mode3]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
......@@ -98,14 +98,15 @@ class CourseModeModelTest(TestCase):
modes = CourseMode.modes_for_course(self.course_id)
self.assertEqual([CourseMode.DEFAULT_MODE], modes)
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd')
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None)
self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices)
modes = CourseMode.modes_for_course(self.course_id)
self.assertEqual([mode1], modes)
expired_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=1)
expiration_date = datetime.now(pytz.UTC) + timedelta(days=1)
expired_mode.expiration_date = expiration_date
expired_mode.save()
expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', expiration_date.date())
modes = CourseMode.modes_for_course(self.course_id)
self.assertEqual([expired_mode_value, mode1], modes)
......
......@@ -34,10 +34,19 @@ class ChooseModeView(View):
@method_decorator(login_required)
def get(self, request, course_id, error=None):
""" Displays the course mode choice page """
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(request.user, course_id)
upgrade = request.GET.get('upgrade', False)
# verified users do not need to register or upgrade
if enrollment_mode == 'verified':
return redirect(reverse('dashboard'))
modes = CourseMode.modes_for_course_dict(course_id)
# registered users who are not trying to upgrade do not need to re-register
if enrollment_mode is not None and upgrade is False:
return redirect(reverse('dashboard'))
modes = CourseMode.modes_for_course_dict(course_id)
donation_for_course = request.session.get("donation_for_course", {})
chosen_price = donation_for_course.get(course_id, None)
......@@ -50,6 +59,7 @@ class ChooseModeView(View):
"course_num": course.display_number_with_default,
"chosen_price": chosen_price,
"error": error,
"upgrade": upgrade,
}
if "verified" in modes:
context["suggested_prices"] = [decimal.Decimal(x) for x in modes["verified"].suggested_prices.split(",")]
......@@ -70,6 +80,8 @@ class ChooseModeView(View):
error_msg = _("Enrollment is closed")
return self.get(request, course_id, error=error_msg)
upgrade = request.GET.get('upgrade', False)
requested_mode = self.get_requested_mode(request.POST.get("mode"))
if requested_mode == "verified" and request.POST.get("honor-code"):
requested_mode = "honor"
......@@ -106,13 +118,12 @@ class ChooseModeView(View):
if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
return redirect(
reverse('verify_student_verified',
kwargs={'course_id': course_id})
kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade)
)
return redirect(
reverse('verify_student_show_requirements',
kwargs={'course_id': course_id}),
)
kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade))
def get_requested_mode(self, user_choice):
"""
......@@ -121,6 +132,7 @@ class ChooseModeView(View):
"""
choices = {
"Select Audit": "audit",
"Select Certificate": "verified"
"Select Certificate": "verified",
"Upgrade Your Registration": "verified"
}
return choices.get(user_choice)
......@@ -2,6 +2,7 @@ from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed, CourseEnrollment,
PendingEmailChange, UserStanding,
)
from course_modes.models import CourseMode
from django.contrib.auth.models import Group
from datetime import datetime
from factory import DjangoModelFactory, SubFactory, PostGenerationMethodCall, post_generation, Sequence
......@@ -36,6 +37,16 @@ class UserProfileFactory(DjangoModelFactory):
goals = u'World domination'
class CourseModeFactory(DjangoModelFactory):
FACTORY_FOR = CourseMode
course_id = None
mode_display_name = u'Honor Code',
mode_slug = 'honor'
min_price = 0
suggested_prices = ''
currency = 'usd'
class RegistrationFactory(DjangoModelFactory):
FACTORY_FOR = Registration
......
......@@ -8,6 +8,8 @@ import logging
import json
import re
import unittest
from datetime import datetime, timedelta
import pytz
from django.conf import settings
from django.test import TestCase
......@@ -28,8 +30,8 @@ from textwrap import dedent
from student.models import unique_id_for_user, CourseEnrollment
from student.views import (process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper,
change_enrollment)
from student.tests.factories import UserFactory
change_enrollment, complete_course_mode_info)
from student.tests.factories import UserFactory, CourseModeFactory
from student.tests.test_email import mock_render_to_string
import shoppingcart
......@@ -216,6 +218,45 @@ class CourseEndingTest(TestCase):
})
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class DashboardTest(TestCase):
"""
Tests for dashboard utility functions
"""
# arbitrary constant
COURSE_SLUG = "100"
COURSE_NAME = "test_course"
COURSE_ORG = "EDX"
def setUp(self):
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
self.assertIsNotNone(self.course)
self.user = UserFactory.create(username="jack", email="jack@fake.edx.org")
CourseModeFactory.create(
course_id=self.course.id,
mode_slug='honor',
mode_display_name='Honor Code',
)
def test_course_mode_info(self):
verified_mode = CourseModeFactory.create(
course_id=self.course.id,
mode_slug='verified',
mode_display_name='Verified',
expiration_date=(datetime.now(pytz.UTC) + timedelta(days=1)).date()
)
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
course_mode_info = complete_course_mode_info(self.course.id, enrollment)
self.assertTrue(course_mode_info['show_upsell'])
self.assertEquals(course_mode_info['days_for_upsell'], 1)
verified_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=-1)
verified_mode.save()
course_mode_info = complete_course_mode_info(self.course.id, enrollment)
self.assertFalse(course_mode_info['show_upsell'])
self.assertIsNone(course_mode_info['days_for_upsell'])
class EnrollInCourseTest(TestCase):
"""Tests enrolling and unenrolling in courses."""
......
......@@ -267,6 +267,29 @@ def register_user(request, extra_context=None):
return render_to_response('register.html', context)
def complete_course_mode_info(course_id, enrollment):
"""
We would like to compute some more information from the given course modes
and the user's current enrollment
Returns the given information:
- whether to show the course upsell information
- numbers of days until they can't upsell anymore
"""
modes = CourseMode.modes_for_course_dict(course_id)
mode_info = {'show_upsell': False, 'days_for_upsell': None}
# we want to know if the user is already verified and if verified is an
# option
if 'verified' in modes and enrollment.mode != 'verified':
mode_info['show_upsell'] = True
# if there is an expiration date, find out how long from now it is
if modes['verified'].expiration_date:
today = datetime.datetime.now(UTC).date()
mode_info['days_for_upsell'] = (modes['verified'].expiration_date - today).days
return mode_info
@login_required
@ensure_csrf_cookie
def dashboard(request):
......@@ -300,6 +323,7 @@ def dashboard(request):
show_courseware_links_for = frozenset(course.id for course, _enrollment in courses
if has_access(request.user, course, 'load'))
course_modes = {course.id: complete_course_mode_info(course.id, enrollment) for course, enrollment in courses}
cert_statuses = {course.id: cert_info(request.user, course) for course, _enrollment in courses}
# only show email settings for Mongo course and when bulk email is turned on
......@@ -324,6 +348,7 @@ def dashboard(request):
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_modes,
'cert_statuses': cert_statuses,
'show_email_settings_for': show_email_settings_for,
}
......
......@@ -2,8 +2,16 @@
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../main.html" />
<%block name="bodyclass">register verification-process step-select-track</%block>
<%block name="title"><title>${_("Register for {} | Choose Your Track").format(course_name)}</title></%block>
<%block name="bodyclass">register verification-process step-select-track ${'is-upgrading' if upgrade else ''}</%block>
<%block name="title">
<title>
%if upgrade:
${_("Upgrade Your Registration for {} | Choose Your Track").format(course_name)}
%else:
${_("Register for {} | Choose Your Track").format(course_name)}
%endif
</title>
</%block>
<%block name="js_extra">
<script type="text/javascript">
......@@ -48,7 +56,10 @@ $(document).ready(function() {
<div class="wrapper-register-choose wrapper-content-main">
<article class="register-choose content-main">
%if not upgrade:
<h3 class="title">${_("Select your track:")}</h3>
%endif
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
......@@ -57,9 +68,16 @@ $(document).ready(function() {
<div class="wrapper-copy">
<span class="deco-ribbon"></span>
<h4 class="title">${_("Certificate of Achievement (ID Verified)")}</h4>
<div class="copy">
<p>${_("Sign up and work toward a verified Certificate of Achievement.")}</p>
</div>
%if upgrade:
<div class="copy">
<p>${_("Upgrade and work toward a verified Certificate of Achievement.")}</p>
</div>
%else:
<div class="copy">
<p>${_("Sign up and work toward a verified Certificate of Achievement.")}</p>
</div>
%endif
</div>
<div class="field field-certificate-contribution">
......@@ -115,16 +133,28 @@ $(document).ready(function() {
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="mode" value="Select Certificate" />
%if upgrade:
<input type="submit" name="mode" value="Upgrade Your Registration" />
%else:
<input type="submit" name="mode" value="Select Certificate" />
%endif
</li>
</ul>
</div>
<div class="help help-register">
<h3 class="title">${_("Verified Registration Requirements")}</h3>
<div class="copy">
<p>${_("To register for a Verified Certificate of Achievement option, you will need a webcam, a credit or debit card, and an ID.")}</p>
</div>
%if upgrade:
<div class="copy">
<p>${_("To upgrade your registration and work towards a Verified Certificate of Achievement, you will need a webcam, a credit or debit card, and an ID.")}</p>
</div>
%else:
<div class="copy">
<p>${_("To register for a Verified Certificate of Achievement option, you will need a webcam, a credit or debit card, and an ID.")}</p>
</div>
%endif
<h3 class="title">${_("What is an ID Verified Certificate?")}</h3>
<div class="copy">
......@@ -133,25 +163,29 @@ $(document).ready(function() {
</div>
% endif
% if "audit" in modes:
<span class="deco-divider">
<span class="copy">${_("or")}</span>
</span>
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<h4 class="title">${_("Audit This Course")}</h4>
<div class="copy">
<p>${_("Sign up to audit this course for free and track your own progress.")}</p>
%if not upgrade:
% if "audit" in modes:
<span class="deco-divider">
<span class="copy">${_("or")}</span>
</span>
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<h4 class="title">${_("Audit This Course")}</h4>
<div class="copy">
<p>${_("Sign up to audit this course for free and track your own progress.")}</p>
</div>
</div>
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="mode" value="Select Audit" />
</li>
</ul>
</div>
% endif
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="mode" value="Select Audit" />
</li>
</ul>
</div>
% endif
%endif
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
</form>
......
......@@ -35,6 +35,7 @@ Feature: LMS.Verified certificates
And I navigate to my dashboard
Then I see the course on my dashboard
And I see that I am on the verified track
And I do not see the upsell link on my dashboard
# Not easily automated
# Scenario: I can re-take photos
......@@ -70,3 +71,24 @@ Feature: LMS.Verified certificates
And the course has an honor mode
When I give a reason why I cannot pay
Then I should see the course on my dashboard
Scenario: The upsell offer is on the dashboard if I am auditing.
Given I am logged in
When I select the audit track
And I navigate to my dashboard
Then I see the upsell link on my dashboard
Scenario: I can take the upsell offer and pay for it
Given I am logged in
And I select the audit track
And I navigate to my dashboard
When I see the upsell link on my dashboard
And I select the upsell link on my dashboard
And I select the verified track for upgrade
And I submit my photos and confirm
And I am at the payment page
And I submit valid payment information
And I navigate to my dashboard
Then I see the course on my dashboard
And I see that I am on the verified track
......@@ -6,6 +6,8 @@ from lettuce.django import django_url
from course_modes.models import CourseMode
from nose.tools import assert_equal
UPSELL_LINK_CSS = '.message-upsell a.action-upgrade[href*="edx/999/Certificates"]'
def create_cert_course():
world.clear_courses()
org = 'edx'
......@@ -53,7 +55,7 @@ def the_course_has_an_honor_mode(step):
mode_slug='honor',
mode_display_name='honor mode',
min_price=0,
)
)
assert isinstance(honor_mode, CourseMode)
......@@ -73,14 +75,28 @@ def select_contribution(amount=32):
assert world.css_find(radio_css).selected
def click_verified_track_button():
world.wait_for_ajax_complete()
btn_css = 'input[value="Select Certificate"]'
world.css_click(btn_css)
@step(u'I select the verified track for upgrade')
def select_verified_track_upgrade(step):
select_contribution(32)
world.wait_for_ajax_complete()
btn_css = 'input[value="Upgrade Your Registration"]'
world.css_click(btn_css)
# TODO: might want to change this depending on the changes for upgrade
assert world.is_css_present('section.progress')
@step(u'I select the verified track$')
def select_the_verified_track(step):
create_cert_course()
register()
select_contribution(32)
world.wait_for_ajax_complete()
btn_css = 'input[value="Select Certificate"]'
world.css_click(btn_css)
click_verified_track_button()
assert world.is_css_present('section.progress')
......@@ -203,6 +219,20 @@ def submitted_photos_to_verify_my_identity(step):
step.given('I go to step "4"')
@step(u'I submit my photos and confirm')
def submit_photos_and_confirm(step):
step.given('I go to step "1"')
step.given('I capture my "face" photo')
step.given('I approve my "face" photo')
step.given('I go to step "2"')
step.given('I capture my "photo_id" photo')
step.given('I approve my "photo_id" photo')
step.given('I go to step "3"')
step.given('I select a contribution amount')
step.given('I confirm that the details match')
step.given('I go to step "4"')
@step(u'I see that my payment was successful')
def see_that_my_payment_was_successful(step):
title = world.css_find('div.wrapper-content-main h3.title')
......@@ -221,6 +251,27 @@ def see_the_course_on_my_dashboard(step):
assert world.is_css_present(course_link_css)
@step(u'I see the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step):
course_link_css = UPSELL_LINK_CSS
assert world.is_css_present(course_link_css)
@step(u'I do not see the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step):
course_link_css = UPSELL_LINK_CSS
assert not world.is_css_present(course_link_css)
@step(u'I select the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step):
# expand the upsell section
world.css_click('.message-upsell')
course_link_css = UPSELL_LINK_CSS
# click the actual link
world.css_click(course_link_css)
@step(u'I see that I am on the verified track')
def see_that_i_am_on_the_verified_track(step):
id_verified_css = 'li.course-item article.course.verified'
......
......@@ -44,12 +44,15 @@ class VerifyView(View):
before proceeding to payment
"""
upgrade = request.GET.get('upgrade', False)
# If the user has already been verified within the given time period,
# redirect straight to the payment -- no need to verify again.
if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
return redirect(
reverse('verify_student_verified',
kwargs={'course_id': course_id}))
kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade)
)
elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
return redirect(reverse('dashboard'))
else:
......@@ -85,6 +88,7 @@ class VerifyView(View):
"currency": verify_mode.currency.upper(),
"chosen_price": chosen_price,
"min_price": verify_mode.min_price,
"upgrade": upgrade,
}
return render_to_response('verify_student/photo_verification.html', context)
......@@ -100,6 +104,7 @@ class VerifiedView(View):
"""
Handle the case where we have a get request
"""
upgrade = request.GET.get('upgrade', False)
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
return redirect(reverse('dashboard'))
verify_mode = CourseMode.mode_for_course(course_id, "verified")
......@@ -117,6 +122,7 @@ class VerifiedView(View):
"purchase_endpoint": get_purchase_endpoint(),
"currency": verify_mode.currency.upper(),
"chosen_price": chosen_price,
"upgrade": upgrade,
}
return render_to_response('verify_student/verified.html', context)
......@@ -250,6 +256,7 @@ def show_requirements(request, course_id):
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified':
return redirect(reverse('dashboard'))
upgrade = request.GET.get('upgrade', False)
course = course_from_id(course_id)
context = {
"course_id": course_id,
......@@ -257,5 +264,6 @@ def show_requirements(request, course_id):
"course_org": course.display_org_with_default,
"course_num": course.display_number_with_default,
"is_not_active": not request.user.is_active,
"upgrade": upgrade,
}
return render_to_response("verify_student/show_requirements.html", context)
......@@ -100,3 +100,16 @@
// outline: thin dotted !important;
}
}
// removes list styling/spacing when using uls, ols for navigation and less content-centric cases
%ui-no-list {
list-style: none;
margin: 0;
padding: 0;
text-indent: 0;
li {
margin: 0;
padding: 0;
}
}
......@@ -282,4 +282,5 @@
border-radius: ($baseline/5);
padding: ($baseline/2) $baseline;
text-transform: uppercase;
letter-spacing: 0.1rem;
}
......@@ -227,6 +227,8 @@
}
}
// ====================
// course listings
.my-courses {
float: left;
......@@ -272,6 +274,8 @@
}
}
// ====================
// UI: course list
.listing-courses {
@extend %ui-no-list;
......@@ -289,6 +293,8 @@
}
}
// ====================
// UI: individual course item
.course {
@include box-sizing(box);
......@@ -416,25 +422,27 @@
}
}
// ====================
// STATE: course mode - verified
&.verified {
@extend %ui-depth2;
margin-top: ($baseline*2.5);
border-top: 1px solid $verified-color-lvl3;
padding-top: ($baseline*1.25);
background: $white;
position: relative;
// FIXME: bad, but needed selector!
.info > hgroup .date-block {
top: ($baseline*1.25);
.cover {
border-radius: ($baseline/10);
border: 1px solid $verified-color-lvl3;
border-bottom: 4px solid $verified-color-lvl3;
padding: ($baseline/10);
}
// course enrollment status message
.sts-enrollment {
display: inline-block;
position: absolute;
top: -28px;
right: ($baseline/2);
top: 105px;
left: 55px;
bottom: ($baseline/2);
text-align: center;
.label {
......@@ -454,53 +462,176 @@
@extend %copy-badge;
border-radius: 0;
padding: ($baseline/4) ($baseline/2) ($baseline/4) $baseline;
color: $white;
background: $verified-color-lvl3;
color: $white;
}
}
}
}
.message-status {
// ====================
// UI: message
.message {
@include clearfix;
border-radius: 3px;
display: none;
z-index: 10;
margin: 20px 0 10px;
padding: 15px 20px;
margin: $baseline 0 ($baseline/2) 0;
padding: ($baseline*0.75) $baseline;
font-family: $sans-serif;
background: tint($yellow,70%);
border: 1px solid #ccc;
.message-copy {
// STATE: shown
&.is-shown {
display: block;
}
a {
font-family: $sans-serif;
font-size: 13px;
margin: 0;
}
strong {
font-weight: 700;
a {
font-family: $sans-serif;
font-weight: 700;
}
}
.grade-value {
font-size: 1.2rem;
font-weight: bold;
.actions {
@include clearfix;
list-style: none;
margin: 0;
padding: 0;
}
.message-title,
.message-copy .title {
@extend %t-title5;
@extend %t-weight4;
margin-bottom: ($baseline/4);
}
.message-copy,
.message-copy .copy {
@extend %t-copy-sub1;
margin: 0;
}
// CASE: expandable
&.is-expandable {
.wrapper-tip {
.message-title, .message-copy {
@include transition(color 0.25s ease-in-out 0);
margin-bottom: 0;
}
// STATE: hover
&:hover {
cursor: pointer;
.message-title, .message-copy {
color: $link-color;
}
}
}
strong {
font-weight: 700;
.wrapper-extended {
@include transition(opacity 0.25s ease-in-out 0);
display: none;
opacity: 0.0;
}
a {
font-weight: 700;
// STATE: is expanded
&.is-expanded {
.wrapper-extended {
display: block;
opacity: 1.0;
}
}
}
}
.actions {
@include clearfix;
list-style: none;
// TYPE: upsell
.message-upsell {
.wrapper-tip {
@include clearfix();
.message-title {
float: left;
}
.message-copy {
float: right;
}
}
.wrapper-extended {
padding: ($baseline/2) 0;
.message-copy {
margin-bottom: $baseline;
}
}
.action-upgrade {
@extend %btn-primary-green;
@include clearfix();
position: relative;
left: ($baseline/2);
padding: 8px $baseline 8px ($baseline*2);
.deco-graphic {
position: absolute;
top: -($baseline/4);
left: -($baseline*0.75);
width: ($baseline*2);
}
span {
color: $white; // nasty but needed override for poor <span> styling
}
.copy, .copy-sub {
display: inline-block;
vertical-align: middle;
}
.copy {
@extend %t-action3;
@extend %t-weight4;
margin-right: $baseline;
}
.copy-sub {
@extend %t-action4;
opacity: 0.875;
}
}
}
// TYPE: status
.message-status {
background: tint($yellow,70%);
border-color: #ccc;
.message-copy {
@extend %t-copy-sub1;
margin: 0;
padding: 0;
.grade-value {
font-size: 1.2rem;
font-weight: bold;
}
}
.actions {
.action {
float: left;
......@@ -589,10 +720,6 @@
}
}
&.is-shown {
display: block;
}
&.course-status-processing {
}
......@@ -614,7 +741,6 @@
}
}
a.unenroll {
float: right;
display: block;
......
// lms - views - verification flow
// ====================
// MISC: extends - type
// application: canned headings
%hd-lv1 {
@extend %t-title1;
@extend %t-weight1;
color: $m-gray-d4;
margin: 0 0 ($baseline*2) 0;
}
%hd-lv2 {
@extend %t-title4;
@extend %t-weight1;
margin: 0 0 ($baseline*0.75) 0;
border-bottom: 1px solid $m-gray-l4;
padding-bottom: ($baseline/2);
color: $m-gray-d4;
}
%hd-lv3 {
@extend %t-title6;
@extend %t-weight4;
margin: 0 0 ($baseline/4) 0;
color: $m-gray-d4;
}
%hd-lv4 {
@extend %t-title6;
@extend %t-weight2;
margin: 0 0 $baseline 0;
color: $m-gray-d4;
}
%hd-lv5 {
@extend %t-title7;
@extend %t-weight4;
margin: 0 0 ($baseline/4) 0;
color: $m-gray-d4;
}
// application: canned copy
%copy-base {
@extend %t-copy-base;
color: $m-gray-d2;
}
%copy-lead1 {
@extend %t-copy-lead2;
color: $m-gray;
}
%copy-detail {
@extend %t-copy-sub1;
@extend %t-weight3;
color: $m-gray-d1;
}
%copy-metadata {
@extend %t-copy-sub2;
color: $m-gray-d1;
%copy-metadata-value {
@extend %t-weight2;
}
%copy-metadata-value {
@extend %t-weight4;
}
}
// application: canned links
%copy-link {
border-bottom: 1px dotted transparent;
&:hover, &:active {
border-color: $link-color-d1;
}
}
// ====================
// MISC: extends - button
%btn-verify-primary {
@extend %btn-primary-green;
......@@ -72,7 +153,7 @@
// ====================
// VIEW: all verification steps
.register.verification-process {
.verification-process {
// reset: box-sizing (making things so right its scary)
* {
......@@ -179,12 +260,16 @@
// elements - controls
.action-primary {
@extend %btn-primary-blue;
border: none;
// needed for override due to .register a:link styling
border: 0 !important;
color: $white !important;
}
.action-confirm {
@extend %btn-verify-primary;
border: none;
// needed for override due to .register a:link styling
border: 0 !important;
color: $white !important;
}
// ====================
......@@ -382,17 +467,19 @@
margin-right: ($baseline/4);
opacity: 0.80;
color: $white;
letter-spacing: 0.1rem;
}
}
}
.sts-label {
@extend %t-title7;
@extend %t-weight4;
display: block;
margin-bottom: ($baseline/2);
border-bottom: ($baseline/10) solid $m-gray-l4;
padding-bottom: ($baseline/2);
color: $m-gray;
color: $m-gray-d1;
}
.sts-course {
......@@ -1816,3 +1903,11 @@
width: 32% !important;
}
}
// STATE: upgrading registration type
.register.is-upgrading {
.form-register-choose {
margin-top: ($baseline*2) !important;
}
}
......@@ -14,6 +14,14 @@
<script type="text/javascript">
(function() {
$('.message.is-expandable .wrapper-tip').bind('click', toggleExpandMessage);
function toggleExpandMessage(e) {
(e).preventDefault();
$(this).closest('.message.is-expandable').toggleClass('is-expanded');
}
$(".email-settings").click(function(event) {
$("#email_settings_course_id").val( $(event.target).data("course-id") );
$("#email_settings_course_number").text( $(event.target).data("course-number") );
......@@ -179,7 +187,8 @@
<% show_courseware_link = (course.id in show_courseware_links_for) %>
<% cert_status = cert_statuses.get(course.id) %>
<% show_email_settings = (course.id in show_email_settings_for) %>
<%include file='dashboard/dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings" />
<% course_mode_info = all_course_modes.get(course.id) %>
<%include file='dashboard/dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info" />
% endfor
</ul>
......
<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings" />
<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info" />
<%! from django.utils.translation import ugettext as _ %>
<%!
......@@ -29,11 +29,11 @@
% endif
% if enrollment.mode == "verified":
<span class="sts-enrollment">
<span class="label">${_("Enrolled as: ")}</span>
<img class="deco-graphic" src="${static.url('images/vcert-ribbon-s.png')}" alt="ID Verified Ribbon/Badge">
<span class="sts-enrollment-value">${_("ID Verified")}</span>
</span>
<span class="sts-enrollment">
<span class="label">${_("Enrolled as: ")}</span>
<img class="deco-graphic" src="${static.url('images/vcert-ribbon-s.png')}" alt="ID Verified Ribbon/Badge" />
<span class="sts-enrollment-value">${_("ID Verified")}</span>
</span>
% endif
<section class="info">
......@@ -95,12 +95,12 @@
<li class="action">
<a class="btn" href="${cert_status['download_url']}"
title="${_('This link will open/download a PDF document')}">
Download Your PDF Certificate</a></li>
${_("Download Your PDF Certificate")}</a></li>
% endif
% if cert_status['show_survey_button']:
<li class="action"><a class="cta" href="${cert_status['survey_url']}">
${_('Complete our course feedback survey')}</a></li>
${_("Complete our course feedback survey")}</a></li>
% endif
</ul>
% endif
......@@ -108,6 +108,31 @@
% endif
%if course_mode_info['show_upsell']:
<div class="message message-upsell has-actions is-expandable is-shown">
<div class="wrapper-tip">
<h4 class="message-title">${_("Challenge Yourself!")}</h4>
<p class="message-copy">${_("Take this course as an ID-verified student.")}</p>
</div>
<div class="wrapper-extended">
<p class="message-copy">${_("You can still sign up for an ID verified certificate for this course. If you plan to complete the whole course, it is a great way to recognize your achievement. {a_start}Learn more about verified certificates{a_end}.").format(a_start='<a href="{}">'.format(marketing_link('WHAT_IS_VERIFIED_CERT')), a_end="</a>")}</p>
<ul class="actions message-actions">
<li class="action-item">
<a class="action action-upgrade" href="${reverse('course_modes_choose', kwargs={'course_id': course.id})}?upgrade=True">
<img class="deco-graphic" src="${static.url('images/vcert-ribbon-s.png')}" alt="ID Verified Ribbon/Badge">
<span class="wrapper-copy">
<span class="copy">${_("Upgrade to Verified Track")}</span>
</span>
</a>
</li>
</ul>
</div>
</div>
%endif
% if show_courseware_link:
% if course.has_ended():
<a href="${course_target}" class="enter-course archived">${_('View Archived Course')}</a>
......@@ -116,8 +141,6 @@
% endif
% endif
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}">${_('Unregister')}</a>
% if show_email_settings:
......
......@@ -2,7 +2,11 @@
<header class="page-header">
<h2 class="title">
<span class="sts-label">${_("You are registering for")}</span>
%if upgrade:
<span class="sts-label">${_("You are upgrading your registration for")}</span>
%else:
<span class="sts-label">${_("You are registering for")}</span>
%endif
<span class="wrapper-sts">
<span class="sts-course">
......@@ -13,7 +17,11 @@
<span class="sts-track">
<span class="sts-track-value">
<span class="context">${_("Registering as: ")}</span> ${_("ID Verified")}
%if upgrade:
<span class="context">${_("Upgrading to:")}</span> ${_("ID Verified")}
%else:
<span class="context">${_("Registering as: ")}</span> ${_("ID Verified")}
%endif
</span>
</span>
</span>
......
......@@ -11,14 +11,21 @@
</li>
<li class="help-item help-item-coldfeet">
<h3 class="title">${_("Change your mind?")}</h3>
<div class="copy">
<p>${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</p>
</div>
%if upgrade:
<h3 class="title">${_("Change your mind?")}</h3>
<div class="copy">
<p>${_("You can always continue to audit the course without verifying.")}</p>
</div>
%else:
<h3 class="title">${_("Change your mind?")}</h3>
<div class="copy">
<p>${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</p>
</div>
%endif
</li>
<li class="help-item help-item-technical">
<h3 class="title">${_("Having Technical Trouble?")}</h3>
<h3 class="title">${_("Technical Requirements")}</h3>
<div class="copy">
<p>${_("Please make sure your browser is updated to the {strong_start}{a_start}most recent version possible{a_end}{strong_end}. Also, please make sure your {strong_start}web cam is plugged in, turned on, and allowed to function in your web browser (commonly adjustable in your browser settings).{strong_end}").format(a_start='<a rel="external" href="http://browsehappy.com/">', a_end="</a>", strong_start="<strong>", strong_end="</strong>")}</p>
</div>
......
......@@ -3,8 +3,16 @@
<%inherit file="../main.html" />
<%namespace name='static' file='/static_content.html'/>
<%block name="bodyclass">register verification-process step-photos</%block>
<%block name="title"><title>${_("Register for {} | Verification").format(course_name)}</title></%block>
<%block name="bodyclass">register verification-process step-photos ${'is-upgrading' if upgrade else ''}</%block>
<%block name="title">
<title>
%if upgrade:
${_("Upgrade Your Registration for {} | Verification").format(course_name)}
%else:
${_("Register for {} | Verification").format(course_name)}
%endif
</title>
</%block>
<%block name="js_extra">
<script src="${static.url('js/vendor/responsive-carousel/responsive-carousel.js')}"></script>
......@@ -172,7 +180,12 @@
<dt class="faq-question">${_("What do you do with this picture?")}</dt>
<dd class="faq-answer">${_("We only use it to verify your identity. It is not displayed anywhere.")}</dd>
<dt class="faq-question">${_("What if my camera isn't working?")}</dt>
<dd class="faq-answer">${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</dd>
%if upgrade:
<dd class="faq-answer">${_("You can always continue to audit the course without verifying.")}</dd>
%else:
<dd class="faq-answer">${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</dd>
%endif
</dl>
</div>
</div>
......
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../main.html" />
<%block name="bodyclass">register verification-process step-requirements</%block>
<%block name="title"><title>${_("Register for {}").format(course_name)}</title></%block>
<%block name="bodyclass">register verification-process step-requirements ${'is-upgrading' if upgrade else ''}</%block>
<%block name="title">
<title>
%if upgrade:
${_("Upgrade Your Registration for {}").format(course_name)}
%else:
${_("Register for {}").format(course_name)}
%endif
</title>
</%block>
<%block name="content">
%if is_not_active:
......@@ -71,11 +79,19 @@
<div class="wrapper-content-main">
<article class="content-main">
<h3 class="title">${_("What You Will Need to Register")}</h3>
%if upgrade:
<h3 class="title">${_("What You Will Need to Upgrade")}</h3>
<div class="instruction">
<p>${_("There are three things you will need to upgrade to being an ID verified student:")}</p>
</div>
%else:
<h3 class="title">${_("What You Will Need to Register")}</h3>
<div class="instruction">
<p>${_("There are three things you will need to register as an ID verified student:")}</p>
</div>
<div class="instruction">
<p>${_("There are three things you will need to register as an ID verified student:")}</p>
</div>
%endif
<ul class="list-reqs ${"account-not-activated" if is_not_active else ""}">
%if is_not_active:
......@@ -149,11 +165,16 @@
</ul>
<nav class="nav-wizard ${"is-not-ready" if is_not_active else "is-ready"}">
<span class="help help-inline">${_("Missing something? You can always {a_start} audit this course instead {a_end}").format(a_start='<a href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</span>
%if upgrade:
<span class="help help-inline">${_("Missing something? You can always continue to audit this course instead.")}</span>
%else:
<span class="help help-inline">${_("Missing something? You can always {a_start} audit this course instead {a_end}").format(a_start='<a href="/course_modes/choose/' + course_id + '">', a_end="</a>")}</span>
%endif
<ol class="wizard-steps">
<li class="wizard-step">
<a class="next action-primary ${"disabled" if is_not_active else ""}" id="face_next_button" href="${reverse('verify_student_verify', kwargs={'course_id': course_id})}">${_("Go to Step 1: Take my Photo")}</a>
<a class="next action-primary ${"disabled" if is_not_active else ""}" id="face_next_button" href="${reverse('verify_student_verify', kwargs={'course_id': course_id})}?upgrade=${upgrade}">${_("Go to Step 1: Take my Photo")}</a>
</li>
</ol>
</nav>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment