Commit b8f7a126 by asadiqbal Committed by zubair-arbi

UI changes on basket page.

parent 4f7b1395
......@@ -17,3 +17,4 @@ Awais Jibran <awaisdar001@gmail.com>
Bill DeRusha <bill@edx.org>
Ivan Ivić <iivic@edx.org>
Saleem Latif <saleem_ee@hotmail.com>
Asad Iqbal <aiqbal@edx.org>
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-22 10:47+0000\n"
"POT-Creation-Date: 2017-12-15 09:43-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -42,7 +42,7 @@ msgid "A valid course ID is required"
msgstr ""
#: ecommerce/static/js/models/coupon_model.js:106
#, c-format
#, javascript-format
msgid "Email domain {%s} is invalid."
msgstr ""
......@@ -253,30 +253,23 @@ msgstr ""
#: ecommerce/static/js/payment_processors/cybersource.js:249
msgid ""
"An error occurred while processing your payment. You have NOT been charged. "
"Please try again, or select another payment method."
msgstr ""
#: ecommerce/static/js/payment_processors/stripe.js:71
msgid ""
"An error occurred while attempting to process your payment. You have not "
"been "
"been charged. Please check your payment details, and try again."
msgstr ""
#: ecommerce/static/js/payment_processors/stripe.js:110
msgid "An error occurred while processing your payment. "
msgid "An error occurred while processing your payment. Please try again."
msgstr ""
#: ecommerce/static/js/utils/utils.js:184
msgid "Trailing comma not allowed."
msgstr ""
#: ecommerce/static/js/utils/validation_patterns.js:18
msgid "The course ID is invalid."
msgstr ""
#: ecommerce/static/js/utils/validation_patterns.js:26
msgid "The product name cannot contain HTML."
msgstr ""
#: ecommerce/static/js/views/coupon_detail_view.js:107
#: ecommerce/static/js/views/coupon_form_view.js:60
msgid "Can be used once by one customer"
......@@ -380,12 +373,15 @@ msgid "Professional Education"
msgstr ""
#: ecommerce/static/js/views/course_form_view.js:66
msgid "Paid certificate track with initial verification and Professional "
msgid ""
"Paid certificate track with initial verification and Professional Education "
"Certificate"
msgstr ""
#: ecommerce/static/js/views/course_form_view.js:72
msgid ""
"Paid certificate track with initial verification and Verified Certificate, "
"and option to purchase credit"
msgstr ""
#. Translators: _START_, _END_, and _TOTAL_ are placeholders. Do NOT translate them.
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-22 10:47+0000\n"
"POT-Creation-Date: 2017-12-15 09:43-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ecommerce/static/js/models/coupon_model.js
......@@ -46,7 +46,7 @@ msgid "A valid course ID is required"
msgstr "À välïd çöürsé ÌD ïs réqüïréd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: ecommerce/static/js/models/coupon_model.js
#, c-format
#, javascript-format
msgid "Email domain {%s} is invalid."
msgstr "Émäïl dömäïn {%s} ïs ïnvälïd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
......@@ -281,38 +281,35 @@ msgstr ""
#: ecommerce/static/js/payment_processors/cybersource.js
msgid ""
"An error occurred while processing your payment. You have NOT been charged. "
"Please try again, or select another payment method."
msgstr ""
"Àn érrör öççürréd whïlé pröçéssïng ýöür päýmént. Ýöü hävé NÖT ßéén çhärgéd."
" Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυ#"
"Àn érrör öççürréd whïlé pröçéssïng ýöür päýmént. Ýöü hävé NÖT ßéén çhärgéd. "
"Pléäsé trý ägäïn, ör séléçt änöthér päýmént méthöd. Ⱡ'σяє#"
#: ecommerce/static/js/payment_processors/stripe.js
msgid ""
"An error occurred while attempting to process your payment. You have not "
"been "
"been charged. Please check your payment details, and try again."
msgstr ""
"Àn érrör öççürréd whïlé ättémptïng tö pröçéss ýöür päýmént. Ýöü hävé nöt "
"ßéén Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#"
"ßéén çhärgéd. Pléäsé çhéçk ýöür päýmént détäïls, änd trý ägäïn. Ⱡ'σяєм ιρѕυм"
" ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя "
"ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ "
"ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ "
"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє "
"¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт "
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт łαвσя#"
#: ecommerce/static/js/payment_processors/stripe.js
msgid "An error occurred while processing your payment. "
msgid "An error occurred while processing your payment. Please try again."
msgstr ""
"Àn érrör öççürréd whïlé pröçéssïng ýöür päýmént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢тєтυя α#"
"Àn érrör öççürréd whïlé pröçéssïng ýöür päýmént. Pléäsé trý ägäïn. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #"
#: ecommerce/static/js/utils/utils.js
msgid "Trailing comma not allowed."
msgstr "Träïlïng çömmä nöt ällöwéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє#"
#: ecommerce/static/js/utils/validation_patterns.js
msgid "The course ID is invalid."
msgstr "Thé çöürsé ÌD ïs ïnvälïd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: ecommerce/static/js/utils/validation_patterns.js
msgid "The product name cannot contain HTML."
msgstr ""
"Thé prödüçt nämé çännöt çöntäïn HTML. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυ#"
#: ecommerce/static/js/views/coupon_detail_view.js
#: ecommerce/static/js/views/coupon_form_view.js
msgid "Can be used once by one customer"
......@@ -428,17 +425,20 @@ msgid "Professional Education"
msgstr "Pröféssïönäl Édüçätïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#"
#: ecommerce/static/js/views/course_form_view.js
msgid "Paid certificate track with initial verification and Professional "
msgid ""
"Paid certificate track with initial verification and Professional Education "
"Certificate"
msgstr ""
"Päïd çértïfïçäté träçk wïth ïnïtïäl vérïfïçätïön änd Pröféssïönäl Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #"
"Päïd çértïfïçäté träçk wïth ïnïtïäl vérïfïçätïön änd Pröféssïönäl Édüçätïön "
"Çértïfïçäté Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє#"
#: ecommerce/static/js/views/course_form_view.js
msgid ""
"Paid certificate track with initial verification and Verified Certificate, "
"and option to purchase credit"
msgstr ""
"Päïd çértïfïçäté träçk wïth ïnïtïäl vérïfïçätïön änd Vérïfïéd Çértïfïçäté, "
"Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυ#"
"Päïd çértïfïçäté träçk wïth ïnïtïäl vérïfïçätïön änd Vérïfïéd Çértïfïçäté, "
"änd öptïön tö pürçhäsé çrédït Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
#. Translators: _START_, _END_, and _TOTAL_ are placeholders. Do NOT translate
#. them.
......
......@@ -47,6 +47,23 @@ def get_lms_courseware_url(course_run_id):
return get_lms_url('courses/{}/info'.format(course_run_id))
def get_lms_course_about_url(course_key):
"""
Return the courseware about URL for the given course key.
Returns:
string: The course about page URL.
"""
return get_lms_url('courses/{}/about'.format(course_key))
def get_lms_explore_courses_url():
"""
Return the explore courses url.
"""
return get_lms_url('courses')
def get_lms_dashboard_url():
site_configuration = _get_site_configuration()
return site_configuration.student_dashboard_url
......
......@@ -19,7 +19,7 @@ from requests.exceptions import ConnectionError, Timeout
from slumber.exceptions import SlumberBaseException
from ecommerce.core.exceptions import SiteConfigurationError
from ecommerce.core.url_utils import get_lms_url
from ecommerce.core.url_utils import get_lms_course_about_url, get_lms_url
from ecommerce.courses.utils import get_certificate_type_display_value, get_course_info_from_catalog
from ecommerce.enterprise.entitlements import get_enterprise_code_redemption_redirect
from ecommerce.enterprise.utils import CONSENT_FAILED_PARAM, get_enterprise_customer_from_voucher, has_enterprise_offer
......@@ -202,6 +202,26 @@ class BasketSummaryView(BasketView):
except (ConnectionError, SlumberBaseException, Timeout):
logger.exception('Failed to retrieve data from Discovery Service for course [%s].', course_key)
if self.request.basket.num_items == 1 and product.is_enrollment_code_product:
course_key = CourseKey.from_string(product.attr.course_key)
course_about = get_lms_course_about_url(course_key=course_key)
messages.info(
self.request,
_(
'{strong_start}Purchasing access just for yourself?{strong_end}{paragraph_start}If you are '
'purchasing a single code for someone else, please continue with checkout. However, if you are the '
'learner {link_start}go back{link_end} to enroll directly.{paragraph_end}'
).format(
strong_start='<strong>',
strong_end='</strong>',
paragraph_start='<p>',
paragraph_end='</p>',
link_start='<a href="{course_about}">'.format(course_about=course_about),
link_end='</a>'
),
extra_tags='safe'
)
return {
'product_title': course_name,
'course_key': course_key,
......@@ -259,8 +279,26 @@ class BasketSummaryView(BasketView):
line_data = self._get_course_data(line.product)
show_voucher_form = False
order_details_msg = _(
'You will receive an email at {user_email} with your enrollment code(s).'
).format(user_email=self.request.user.email)
'{paragraph_start}By purchasing, you and your organization agree to the following terms:'
'{paragraph_end} {ul_start} {li_start}Each code is valid for the one course covered and can be '
'used only one time.{li_end} {li_start}You are responsible for distributing codes to your learners.'
'{li_end} {li_start}Each code will expire in one year from date of purchase or, if earlier, once '
'the course is closed.{li_end} {li_start}If a course is not designated as self-paced, you should '
'confirm that a course run is available before expiration. {li_end} {li_start}You may not resell '
'codes to third parties.{li_end} {ul_end} {strong_start}All sales final. No refunds.{strong_end} '
'{paragraph_start}You will receive an email at {user_email} with your enrollment code(s). '
'{paragraph_end}'
).format(
strong_start='<strong>',
strong_end='</strong>',
paragraph_start='<p>',
paragraph_end='</p>',
ul_start='<ul>',
li_start='<li>',
li_end='</li>',
ul_end='</ul>',
user_email=self.request.user.email
)
else:
line_data = {
'product_title': line.product.title,
......@@ -401,14 +439,16 @@ class BasketSummaryView(BasketView):
)
except ValueError:
total_benefit = None
num_of_items = self.request.basket.num_items
context.update({
'formset_lines_data': zip(formset, lines_data),
'free_basket': context['order_total'].incl_tax == 0,
'homepage_url': get_lms_url(''),
'min_seat_quantity': 1,
'max_seat_quantity': 100,
'payment_processors': payment_processors,
'total_benefit': total_benefit
'total_benefit': total_benefit,
'line_price': (self.request.basket.total_incl_tax_excl_discounts / num_of_items) if num_of_items > 0 else 0
})
return context
......
......@@ -12,7 +12,12 @@ from django.views.generic import RedirectView, TemplateView
from oscar.apps.checkout.views import * # pylint: disable=wildcard-import, unused-wildcard-import
from oscar.core.loading import get_class, get_model
from ecommerce.core.url_utils import get_lms_courseware_url, get_lms_dashboard_url, get_lms_program_dashboard_url
from ecommerce.core.url_utils import (
get_lms_courseware_url,
get_lms_dashboard_url,
get_lms_explore_courses_url,
get_lms_program_dashboard_url
)
from ecommerce.enterprise.utils import has_enterprise_offer
from ecommerce.extensions.checkout.exceptions import BasketNotFreeError
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
......@@ -183,6 +188,12 @@ class ReceiptResponseView(ThankYouView):
})
context.update(self.get_order_dashboard_context(order))
context.update(self.get_order_verification_context(order))
context.update({
'explore_courses_url': get_lms_explore_courses_url(),
'has_enrollment_code_product': any(
line.product.is_enrollment_code_product for line in order.basket.all_lines()
)
})
return context
def get_object(self):
......
......@@ -44,6 +44,9 @@ class PaymentForm(forms.Form):
def __init__(self, user, request, *args, **kwargs):
super(PaymentForm, self).__init__(*args, **kwargs)
self.request = request
self.basket_has_enrollment_code_product = any(
line.product.is_enrollment_code_product for line in self.request.basket.all_lines()
)
update_basket_queryset_filter(self, user)
self.helper = FormHelper(self)
......@@ -100,6 +103,20 @@ class PaymentForm(forms.Form):
self.fields[bound_field.name].label = _('{label} (required)').format(label=bound_field.label)
bound_field.field.widget.attrs['required'] = 'required'
if self.basket_has_enrollment_code_product and 'organization' not in self.fields:
# If basket has any enrollment code items then we will add an organization
# field next to "last_name."
self.fields['organization'] = forms.CharField(max_length=60, label=_('Organization (required)'))
organization_div = Div(
Div(
Div('organization'),
HTML('<p class="help-block"></p>'),
css_class='form-item col-md-6'
),
css_class='row'
)
self.helper.layout.fields.insert(self.fields.keys().index('last_name') + 1, organization_div)
basket = forms.ModelChoiceField(
queryset=Basket.objects.all(),
widget=forms.HiddenInput(),
......@@ -108,16 +125,16 @@ class PaymentForm(forms.Form):
'invalid_choice': _('There was a problem retrieving your basket. Refresh the page to try again.'),
}
)
first_name = forms.CharField(max_length=60, label=_('First Name'))
last_name = forms.CharField(max_length=60, label=_('Last Name'))
address_line1 = forms.CharField(max_length=60, label=_('Address'), required=False)
first_name = forms.CharField(max_length=60, label=_('First Name (required)'))
last_name = forms.CharField(max_length=60, label=_('Last Name (required)'))
address_line1 = forms.CharField(max_length=60, label=_('Address (required)'), required=False)
address_line2 = forms.CharField(max_length=29, required=False, label=_('Suite/Apartment Number'))
city = forms.CharField(max_length=32, label=_('City'))
city = forms.CharField(max_length=32, label=_('City (required)'))
# max_length for state field is set to default 60, if it needs to be changed,
# the equivalent (maxlength) attribute in the basket page JS code needs to be changed too.
state = forms.CharField(max_length=60, required=False, label=_('State/Province'))
postal_code = forms.CharField(max_length=10, required=False, label=_('Zip/Postal Code'))
country = forms.ChoiceField(choices=country_choices, label=_('Country'))
country = forms.ChoiceField(choices=country_choices, label=_('Country (required)'))
def clean_basket(self):
basket = self.cleaned_data['basket']
......
......@@ -2,12 +2,19 @@ from __future__ import unicode_literals
import ddt
import pycountry
from oscar.core.loading import get_model
from oscar.test import factories
from waffle.models import Switch
from ecommerce.core.constants import ENROLLMENT_CODE_PRODUCT_CLASS_NAME, ENROLLMENT_CODE_SWITCH
from ecommerce.core.tests import toggle_switch
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.payment.forms import PaymentForm
from ecommerce.extensions.test.factories import create_basket
from ecommerce.tests.testcases import TestCase
Product = get_model('catalogue', 'Product')
@ddt.ddt
class PaymentFormTests(TestCase):
......@@ -16,6 +23,29 @@ class PaymentFormTests(TestCase):
self.user = self.create_user()
self.basket = create_basket(owner=self.user)
def create_basket_and_add_product(self, product):
basket = factories.BasketFactory(owner=self.user, site=self.site)
basket.add_product(product, 1)
return basket
def prepare_course_seat_and_enrollment_code(self, seat_type='verified', id_verification=False):
"""Helper function that creates a new course, enables enrollment codes and creates a new
seat and enrollment code for it.
Args:
seat_type (str): Seat/certification type.
is_verification (bool): Whether or not id verification is required for the seat.
Returns:
The newly created course, seat and enrollment code.
"""
course = CourseFactory()
toggle_switch(ENROLLMENT_CODE_SWITCH, True)
self.site.siteconfiguration.enable_enrollment_codes = True
self.site.siteconfiguration.save()
seat = course.create_or_update_seat(seat_type, id_verification, 10, self.partner, create_enrollment_code=True)
enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
return course, seat, enrollment_code
def _generate_data(self, **kwargs):
data = {
'basket': self.basket.id,
......@@ -132,3 +162,45 @@ class PaymentFormTests(TestCase):
actual = list(form.fields['country'].choices)
actual.pop(0) # Remove the "Choose country" placeholder
self.assertEqual(actual, expected)
def test_organization_field_in_form(self):
""" Verify the field 'organization' is present in the form when the basket has an enrollment code product. """
__, __, enrollment_code = self.prepare_course_seat_and_enrollment_code()
basket = self.create_basket_and_add_product(enrollment_code)
self.request.basket = basket
data = {
'basket': basket.id,
'first_name': 'Test',
'last_name': 'User',
'address_line1': '141 Portland Ave.',
'address_line2': 'Floor 9',
'city': 'Cambridge',
'state': 'MA',
'postal_code': '02139',
'country': 'US',
}
form = PaymentForm(user=self.user, data=data, request=self.request)
self.assertTrue('organization' in form.fields)
def test_organization_field_not_in_form(self):
"""
Verify the field 'organization' is not present in the form when the basket does not have an enrollment
code product.
"""
course = CourseFactory()
product1 = course.create_or_update_seat("Verified", True, 0, self.partner)
basket = self.create_basket_and_add_product(product1)
self.request.basket = basket
data = {
'basket': basket.id,
'first_name': 'Test',
'last_name': 'User',
'address_line1': '141 Portland Ave.',
'address_line2': 'Floor 9',
'city': 'Cambridge',
'state': 'MA',
'postal_code': '02139',
'country': 'US',
}
form = PaymentForm(user=self.user, data=data, request=self.request)
self.assertFalse('organization' in form.fields)
/* jshint -W065 */
define([
'jquery',
'underscore',
......@@ -79,6 +80,7 @@ define([
'input[name=first_name]',
'input[name=last_name]',
'input[name=city]',
'input[name=organization]',
'select[name=country]'
],
countriesWithRequiredStateAndPostalCodeValues = ['US', 'CA'],
......@@ -148,6 +150,24 @@ define([
}
},
showErrorState: function(e, msg) {
e.preventDefault();
$('#input-quantity-field').addClass('error-state');
$('div#error-msg').text(gettext(msg));
},
validateQuantity: function(e) {
var inputQuantity = $('#input-quantity-field').val();
var quantity = isNaN(parseInt(inputQuantity, 10)) ? '' : parseInt(inputQuantity, 10);
if (quantity === '') {
this.showErrorState(e, 'Please enter a quantity from 1 to 100.');
} else if (quantity > 100) {
this.showErrorState(e, 'Quantity must be less than or equal to 100.');
} else if (quantity < 1) {
this.showErrorState(e, 'Quantity must be greater than or equal to 1.');
}
},
detectCreditCard: function() {
var card,
$input = $('#card-number'),
......@@ -483,6 +503,10 @@ define([
BasketPage.detectCreditCard();
});
$('#quantity-update').on('click', function(e) {
BasketPage.validateQuantity(e);
});
$('#payment-button').click(function(e) {
_.each($('.help-block'), function(errorMsg) {
$(errorMsg).empty(); // Clear existing validation error messages.
......
......@@ -25,7 +25,7 @@
<div aria-labelledby="order-details-region"></div>
<div class="spinner">
<input class="quantity" id="id_form-0-quantity" min="1" max="10" name="form-0-quantity" type="number" value="1">
<input class="quantity" id="input-quantity-field" min="1" max="10" name="form-0-quantity" type="number" value="1">
<div class="input-group-btn-vertical">
<button class="btn btn-primary" type="button">
......@@ -36,6 +36,8 @@
</button>
</div>
</div>
<button id="quantity-update" class="update-button quantity-update" type="submit" data-loading-text="Updating...">Update</button>
<div id="error-msg"></div>
<div id="voucher_form_container">
<input id="id_code">
......
......@@ -153,6 +153,30 @@ define([
expect($quantity.first().val()).toEqual('1');
});
it('should show error message when quantity is greater than 100', function() {
BasketPage.onReady();
$('input.quantity').first().val(101);
$('#quantity-update').trigger('click');
expect($('div#error-msg').text()).toEqual('Quantity must be less than or equal to 100.');
});
it('should show error message when quantity is less than 1', function() {
BasketPage.onReady();
$('input.quantity').first().val(0);
$('#quantity-update').trigger('click');
expect($('div#error-msg').text()).toEqual('Quantity must be greater than or equal to 1.');
});
it('should show error message when quantity is not given', function() {
BasketPage.onReady();
$('input.quantity').first().val('');
$('#quantity-update').trigger('click');
expect($('div#error-msg').text()).toEqual('Please enter a quantity from 1 to 100.');
});
it('should recognize the credit card', function() {
var validCardList = [
{number: '378282246310005', name: 'amex'},
......
......@@ -480,11 +480,31 @@
.quantity-group {
padding-left: 0;
.max-quantity {
font-size: 10px;
color: #4a4a4a;
font-weight: 600;
margin: 0 0 0 85px;
display: block;
}
#error-msg {
font-size: 12px;
color: red;
margin: 5px 0 5px 85px;
}
.error-state {
border-color: red;
}
}
.quantity-label {
display: inline-block;
padding-right: 10px;
vertical-align: top;
margin: 5px 0 0;
}
.checkout-quantity {
......@@ -492,7 +512,7 @@
}
.spinner {
width: 70px;
width: 80px;
float: left;
padding-right: 10px;
}
......@@ -519,18 +539,31 @@
}
.amount {
padding-bottom: 20px;
padding: 0;
color: $gray-color;
font-size: 18px;
span {
float: none !important;
display: block;
text-align: unset;
.price {
font-weight: bold;
&:first-child {
margin: 0 0 20px;
}
}
}
#basket-total {
font-size: 18px;
font-weight: bold;
.price {
font-weight: bold;
text-align: unset;
padding: 0;
}
}
#voucher-information {
......
......@@ -152,6 +152,10 @@
padding: 10px 15px;
}
}
#find-more-courses-link {
margin: 20px;
float: right;
}
.dashboard-link {
line-height: 20px;
......
......@@ -36,11 +36,18 @@
{% captureas link_start %}
<a href="mailto:{{ order.user.email }}">
{% endcaptureas %}
{% blocktrans trimmed with email=order.user.email link_end="</a>" %}
Your order is complete. If you need a receipt, you can print this page.
You will also receive a confirmation message with this information at
{{ link_start }}{{ email }}{{ link_end }}.
{% endblocktrans %}
{% if has_enrollment_code_product %}
{% blocktrans trimmed with email=order.user.email link_end="</a>" %}
Your order is complete. You will receive a confirmation message and your enrollment codes(s) at
{{ link_start }}{{ email }}{{ link_end }}. If you need a receipt, you can print this page.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with email=order.user.email link_end="</a>" %}
Your order is complete. If you need a receipt, you can print this page.
You will also receive a confirmation message with this information at
{{ link_start }}{{ email }}{{ link_end }}.
{% endblocktrans %}
{% endif %}
</div>
{% if order.billing_address %}
......@@ -86,7 +93,7 @@
{% endif %}
</dd>
<dt class="line-price sr">{% trans "Item Price:" %}</dt>
<dd class="line-price price">{{ line.line_price_before_discounts_incl_tax|currency:order.currency }}</dd>
<dd class="line-price price">{{ line.unit_price_incl_tax|currency:order.currency }}</dd>
</div>
{% endfor %}
</dl>
......@@ -165,6 +172,11 @@
</a>
</div>
{% endif %}
<div id="find-more-courses-link">
<a class="find-more-courses-link nav-link" href="{{ explore_courses_url }}" target="_blank">
{% trans "Find more courses" %}
</a>
</div>
</div>
</div>
{% endblock content %}
{% load i18n %}
{% load currency_filters %}
<p>
{% if basket.is_empty %}
{% trans "Your basket is now empty" %}
{% else %}
{% if basket.is_tax_known %}
{% blocktrans with num_items=basket.num_items total=basket.total_incl_tax|currency:basket.currency strong_start='<b>' strong_end='</b>' paragraph_start='<p>' paragraph_end='</p>' %}
{{ strong_start }}We’ve updated your quantity.{{ strong_end }}
{{ paragraph_start }}Your cart includes {{ num_items }} enrollment codes at a total cost of {{ total }}, that you will receive via email.{{ paragraph_end }}
{% endblocktrans %}
{% else %}
{% blocktrans with num_items=basket.num_items total=basket.total_excl_tax|currency:basket.currency strong_start='<b>' strong_end='</b>' paragraph_start='<p>' paragraph_end='</p>' %}
{{ strong_start }}We’ve updated your quantity.{{ strong_end }}
{{ paragraph_start }}Your cart includes {{ num_items }} enrollment codes at a total cost of {{ total }}, that you will receive via email.{{ paragraph_end }}
{% endblocktrans %}
{% endif %}
{% endif %}
</p>
{% if include_buttons %}
<p>
<a href="{% url 'basket:summary' %}" class="btn btn-info">{% trans "View basket" %}</a>
<a href="{% url 'checkout:index' %}" class="btn btn-info">{% trans "Checkout now" %}</a>
</p>
{% endif %}
......@@ -27,14 +27,16 @@
</div>
{% if line_data.enrollment_code %}
<div class="col-sm-12 col-xs-12 form-inline quantity-group">
<label class="product-price-label text-muted quantity-label">{% trans 'Quantity' %}</label>
<label for="input-quantity-field " class="product-price-label text-muted quantity-label">{% trans 'Quantity' %}</label>
<div class="checkout-quantity form-group">
<div class="input-group spinner {% if form.errors %}error{% endif %}">
{% render_field form.quantity class+="quantity form-control" min=min_seat_quantity %}
{% render_field form.quantity class+="quantity form-control" min=min_seat_quantity max=max_seat_quantity title="Quantity" id="input-quantity-field" %}
</div>
<button class="btn btn-primary update-button quantity-update" type="submit"
<button id="quantity-update" class="btn btn-primary update-button quantity-update" type="submit"
data-loading-text="{% trans 'Updating...' %}">{% trans "Update" %}</button>
</div>
<div id="error-msg" role="alert"></div>
<span class="max-quantity">Max: 100</span>
</div>
{% endif %}
</div>
......@@ -43,9 +45,19 @@
</form>
<div role="region" aria-labelledby="summary-region" aria-live="polite">
<h2 id="summary-region" class="title">{% trans "summary" %}</h2>
<div id="line-price" class="amount row">
<span class="description">{% trans "Price" %}</span>
<span class="price">{{basket.total_incl_tax_excl_discounts|currency:basket.currency}}</span>
<div class="row">
<div id="line-price" class="amount col-xs-4 col-sm-4">
<span class="description">{% trans "Price" %}</span>
<span class="price">{{line_price|currency:basket.currency}}</span>
</div>
<div id="line-price" class="amount col-xs-4 col-sm-4">
<span class="description float-none">{% trans "Quanity" %}</span>
<span>{{basket.num_items}}</span>
</div>
<div id="line-price" class="amount col-xs-4 col-sm-4">
<span class="description">{% trans "Subtotal" %}</span>
<span class="price">{{order_total.incl_tax|currency:basket.currency}}</span>
</div>
</div>
{% if basket.total_discount %}
<div id="line-discount" class="amount row">
......@@ -98,14 +110,15 @@
{% endif %}
</div>
<div id="basket-total" class="row" aria-describedby="total-price-desc">
<span id="total-price-desc" class="description">{% trans "TOTAL" %}</span>
<span class="price">{{ order_total.incl_tax|currency:basket.currency }}</span>
<span id="total-price-desc col-xs-4 col-sm-4" class="description">{% trans "TOTAL" %}</span>
<span class="col-xs-4 col-sm-4"></span>
<span class="price col-xs-4 col-sm-4">{{ order_total.incl_tax|currency:basket.currency }}</span>
</div>
</div>
{% if order_details_msg %}
<div role="region" aria-labelledby="order-details-region">
<h2 id="order-details-region" class="title">{% trans "order details" %}</h2>
<p>{{ order_details_msg }}</p>
<p>{{ order_details_msg | safe}}</p>
</div>
{% endif %}
</div>
......
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