From 672d4f8767454faf31e4d1532a5ab57a963c85dd Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 17 Aug 2016 12:11:17 +0100 Subject: [PATCH 01/27] Setup AB test (alternative is still empty). --- services/web/app/views/subscriptions/new.jade | 233 +++++++++--------- 1 file changed, 117 insertions(+), 116 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index e84dc56eec..7613ec463e 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -51,131 +51,132 @@ block content div(ng-if="normalPrice") span.small Normally {{price.currency.symbol}}{{normalPrice}} .row - .col-md-12 - form(ng-show="planName") - - - .row - .col-md-12 - .form-group - .row - .col-md-6 - label.radio-inline - input.paymentTypeOption(type="radio",value="credit_card", ng-model="paymentMethod") - i.fa.fa-cc-mastercard.fa-3x - span   - i.fa.fa-cc-visa.fa-3x - .col-md-6 - label.radio-inline - input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") - i.fa.fa-cc-paypal.fa-3x - - - - .alert.alert-warning.small(ng-show="genericError") - strong {{genericError}} - - span(ng-hide="paymentMethod == 'paypal'") + div(sixpack-switch="subscription-form") + .col-md-12(sixpack-default) + form(ng-show="planName") .row .col-md-12 .form-group - div.alert.alert-warning.small(ng-hide="validation.correctCvv") #{translate("invalid")} CVV - div.alert.alert-warning.small(ng-hide="validation.correctCardNumber") #{translate("invalid")} #{translate("credit_card_number")} + .row + .col-md-6 + label.radio-inline + input.paymentTypeOption(type="radio",value="credit_card", ng-model="paymentMethod") + i.fa.fa-cc-mastercard.fa-3x + span   + i.fa.fa-cc-visa.fa-3x + .col-md-6 + label.radio-inline + input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") + i.fa.fa-cc-paypal.fa-3x + + + + .alert.alert-warning.small(ng-show="genericError") + strong {{genericError}} + + span(ng-hide="paymentMethod == 'paypal'") + .row + .col-md-12 + .form-group + div.alert.alert-warning.small(ng-hide="validation.correctCvv") #{translate("invalid")} CVV + div.alert.alert-warning.small(ng-hide="validation.correctCardNumber") #{translate("invalid")} #{translate("credit_card_number")} + .row + .col-md-6 + .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") + input.form-control(ng-model='data.number', ng-blur="validateCardNumber()", placeholder="#{translate('credit_card_number')}") + .col-md-3 + .form-group(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") + input.form-control(ng-model='data.cvv', ng-blur="validateCvv()", placeholder="CVV") + .row + .col-md-12 + div.alert.alert-warning.small(ng-hide="validation.correctExpiry") #{translate("invalid")} #{translate("expiry")} + .row + .col-md-3 + .form-group(ng-class="validation.correctExpiry == false || validation.errorFields.month ? 'has-error' : ''") + select.form-control(data-recurly='month', ng-change="validateExpiry()", ng-model='data.month') + option(value="", disabled, selected) Month + option(value="01") 01 + option(value="02") 02 + option(value="03") 03 + option(value="04") 04 + option(value="05") 05 + option(value="06") 06 + option(value="07") 07 + option(value="08") 08 + option(value="09") 09 + option(value="10") 10 + option(value="11") 11 + option(value="12") 12 + .col-md-4 + .form-group(ng-class="validation.correctExpiry == false || validation.errorFields.year ? 'has-error' : ''") + select.form-control(data-recurly='year', ng-change="validateExpiry()", ng-model='data.year') + option(value="", disabled, selected) Year + option(value="2016") 2016 + option(value="2017") 2017 + option(value="2018") 2018 + option(value="2019") 2019 + option(value="2020") 2020 + option(value="2021") 2021 + option(value="2022") 2022 + option(value="2023") 2023 + option(value="2024") 2024 + option(value="2025") 2025 + option(value="2026") 2026 .row .col-md-6 - .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") - input.form-control(ng-model='data.number', ng-blur="validateCardNumber()", placeholder="#{translate('credit_card_number')}") - .col-md-3 - .form-group(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") - input.form-control(ng-model='data.cvv', ng-blur="validateCvv()", placeholder="CVV") + .form-group(ng-class="validation.errorFields.first_name ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', , onkeyup='', data-recurly="first_name", ng-model="data.first_name", required, placeholder="#{translate('first_name')}") + .col-md-6 + .form-group(ng-class="validation.errorFields.last_name ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="last_name", ng-model="data.last_name", required, placeholder="#{translate('last_name')}") + hr .row .col-md-12 - div.alert.alert-warning.small(ng-hide="validation.correctExpiry") #{translate("invalid")} #{translate("expiry")} + .form-group(ng-class="validation.errorFields.address1 ? 'has-error' : ''") + label #{translate("billing_address")} + input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.address1", placeholder="#{translate('address')}") + .form-group(ng-class="validation.errorFields.address2 ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.address2", placeholder="#{translate('address')}") + .form-group(ng-class="validation.errorFields.state ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.state", placeholder="#{translate('state')}") .row - .col-md-3 - .form-group(ng-class="validation.correctExpiry == false || validation.errorFields.month ? 'has-error' : ''") - select.form-control(data-recurly='month', ng-change="validateExpiry()", ng-model='data.month') - option(value="", disabled, selected) Month - option(value="01") 01 - option(value="02") 02 - option(value="03") 03 - option(value="04") 04 - option(value="05") 05 - option(value="06") 06 - option(value="07") 07 - option(value="08") 08 - option(value="09") 09 - option(value="10") 10 - option(value="11") 11 - option(value="12") 12 - .col-md-4 - .form-group(ng-class="validation.correctExpiry == false || validation.errorFields.year ? 'has-error' : ''") - select.form-control(data-recurly='year', ng-change="validateExpiry()", ng-model='data.year') - option(value="", disabled, selected) Year - option(value="2016") 2016 - option(value="2017") 2017 - option(value="2018") 2018 - option(value="2019") 2019 - option(value="2020") 2020 - option(value="2021") 2021 - option(value="2022") 2022 - option(value="2023") 2023 - option(value="2024") 2024 - option(value="2025") 2025 - option(value="2026") 2026 - .row - .col-md-6 - .form-group(ng-class="validation.errorFields.first_name ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', , onkeyup='', data-recurly="first_name", ng-model="data.first_name", required, placeholder="#{translate('first_name')}") - .col-md-6 - .form-group(ng-class="validation.errorFields.last_name ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="last_name", ng-model="data.last_name", required, placeholder="#{translate('last_name')}") - hr - .row - .col-md-12 - .form-group(ng-class="validation.errorFields.address1 ? 'has-error' : ''") - label #{translate("billing_address")} - input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.address1", placeholder="#{translate('address')}") - .form-group(ng-class="validation.errorFields.address2 ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.address2", placeholder="#{translate('address')}") - .form-group(ng-class="validation.errorFields.state ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', onkeyup='', ng-model="data.state", placeholder="#{translate('state')}") - .row - .col-md-7 - .form-group(ng-class="validation.errorFields.city ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="city", ng-model="data.city", placeholder="#{translate('city')}") - .col-md-5(ng-class="validation.errorFields.postal_code ? 'has-error' : ''") - input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="postal_code", ng-model="data.postal_code", placeholder="#{translate('zip_post_code')}") - .row - .col-md-7 - .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") - select.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) - mixin countries_options() - .row - .col-md-8 - if (showCouponField) + .col-md-7 + .form-group(ng-class="validation.errorFields.city ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="city", ng-model="data.city", placeholder="#{translate('city')}") + .col-md-5(ng-class="validation.errorFields.postal_code ? 'has-error' : ''") + input.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="postal_code", ng-model="data.postal_code", placeholder="#{translate('zip_post_code')}") + .row + .col-md-7 + .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") + select.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) + mixin countries_options() + .row + .col-md-8 + if (showCouponField) + .form-group + input.form-control(type='text', ng-blur="applyCoupon()", ng-model="data.coupon", placeholder="#{translate('coupon')}") + .row + .col-md-8 + if (showVatField) + .form-group + input.form-control(type='text', ng-blur="applyVatNumber()", ng-model="data.vat_number", placeholder="#{translate('vat_number')}") + .row + .col-xs-7 .form-group - input.form-control(type='text', ng-blur="applyCoupon()", ng-model="data.coupon", placeholder="#{translate('coupon')}") - .row - .col-md-8 - if (showVatField) - .form-group - input.form-control(type='text', ng-blur="applyVatNumber()", ng-model="data.vat_number", placeholder="#{translate('vat_number')}") - .row - .col-xs-7 - .form-group - button.btn.btn-success(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") #{translate("upgrade_now")} - - .col-xs-3.pricingBreakdown - div(ng-if="price.next.subtotal != price.next.total") Subtotal - div(ng-if="price.next.tax!='0.00'") Tax - div - strong Total - .col-xs-2 - div(ng-if="price.next.subtotal != price.next.total").pull-right {{price.currency.symbol}}{{price.next.subtotal}} - div(ng-if="price.next.tax!='0.00'").pull-right {{price.currency.symbol}}{{price.next.tax}} - div.pull-right - strong {{price.currency.symbol}}{{price.next.total}} + button.btn.btn-success(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") #{translate("upgrade_now")} + + .col-xs-3.pricingBreakdown + div(ng-if="price.next.subtotal != price.next.total") Subtotal + div(ng-if="price.next.tax!='0.00'") Tax + div + strong Total + .col-xs-2 + div(ng-if="price.next.subtotal != price.next.total").pull-right {{price.currency.symbol}}{{price.next.subtotal}} + div(ng-if="price.next.tax!='0.00'").pull-right {{price.currency.symbol}}{{price.next.tax}} + div.pull-right + strong {{price.currency.symbol}}{{price.next.total}} + .col-md-12(sixpack-when="simple") + span Lorem ipsum! span(sixpack-switch="payment-left-menu-bottom") From 79d9e5445899225fb9b6aae6d84dafcde9bed2f2 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 17 Aug 2016 17:34:04 +0100 Subject: [PATCH 02/27] Basic port of Stripe JS credit card validation and formatting lib. --- .../coffee/directives/creditCards.coffee | 395 ++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 services/web/public/coffee/directives/creditCards.coffee diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee new file mode 100644 index 0000000000..ae9df139a9 --- /dev/null +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -0,0 +1,395 @@ +define [ + "base" +], (App) -> + App.factory 'ccUtils', () -> + defaultFormat = /(\d{1,4})/g; + defaultInputFormat = /(?:^|\s)(\d{4})$/ + + cards = [ + # Credit cards + { + type: 'visa' + patterns: [4] + format: defaultFormat + length: [13, 16] + cvcLength: [3] + luhn: true + } + { + type: 'mastercard' + patterns: [ + 51, 52, 53, 54, 55, + 22, 23, 24, 25, 26, 27 + ] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + { + type: 'amex' + patterns: [34, 37] + format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/ + length: [15] + cvcLength: [3..4] + luhn: true + } + { + type: 'dinersclub' + patterns: [30, 36, 38, 39] + format: /(\d{1,4})(\d{1,6})?(\d{1,4})?/ + length: [14] + cvcLength: [3] + luhn: true + } + { + type: 'discover' + patterns: [60, 64, 65, 622] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + { + type: 'unionpay' + patterns: [62, 88] + format: defaultFormat + length: [16..19] + cvcLength: [3] + luhn: false + } + { + type: 'jcb' + patterns: [35] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + ] + + cardFromNumber = (num) -> + num = (num + '').replace(/\D/g, "") + for card in cards + for pattern in card.patterns + p = pattern + "" + return card if num.substr(0, p.length) == p + + cardFromType = (type) -> + return card for card in cards when card.type is type + + parseExpiry = (value = "") -> + [month, year] = value.split(/[\s\/]+/, 2) + + # Allow for year shortcut + if year?.length is 2 and /^\d+$/.test(year) + prefix = (new Date).getFullYear() + prefix = prefix.toString()[0..1] + year = prefix + year + + month = parseInt(month, 10) + year = parseInt(year, 10) + + month: month, year: year + + return { + fromNumber: cardFromNumber + fromType: cardFromType + defaultFormat: defaultFormat + defaultInputFormat: defaultInputFormat + parseExpiry: parseExpiry + } + + App.factory 'ccFormat', (ccUtils, $filter) -> + hasTextSelected = ($target) -> + # If some text is selected + return true if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt $target.prop('selectionEnd') + + # If some text is selected in IE + if document?.selection?.createRange? + return true if document.selection.createRange().text + + false + + # Replace Full-Width Chars + replaceFullWidthChars = (str = '') -> + fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' + halfWidth = '0123456789' + + value = '' + chars = str.split('') + + # Avoid using reserved word `char` + for chr in chars + idx = fullWidth.indexOf(chr) + chr = halfWidth[idx] if idx > -1 + value += chr + + value + + # Format Numeric + reFormatNumeric = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = value.replace(/\D/g, '') + safeVal(value, $target) + + # Format Card Number + reFormatCardNumber = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = $.payment.formatCardNumber(value) + #safeVal(value, $target) + + formatCardNumber = (e) -> + # Only format if input is a number + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + value = $target.val() + card = ccUtils.cardFromNumber(value + digit) + length = (value.replace(/\D/g, '') + digit).length + + upperLength = 16 + upperLength = card.length[card.length.length - 1] if card + return if length >= upperLength + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + if card && card.type is 'amex' + # AMEX cards are formatted differently + re = /^(\d{4}|\d{4}\s\d{6})$/ + else + re = /(?:^|\s)(\d{4})$/ + + # If '4242' + 4 + if re.test(value) + e.preventDefault() + setTimeout -> $target.val(value + ' ' + digit) + + # If '424' + 2 + else if re.test(value + digit) + e.preventDefault() + setTimeout -> $target.val(value + digit + ' ') + + formatBackCardNumber = (e) -> + $target = $(e.currentTarget) + value = $target.val() + + # Return unless backspacing + return unless e.which is 8 + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + # Remove the digit + trailing space + if /\d\s$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d\s$/, '')) + # Remove digit if ends in space + digit + else if /\s\d?$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d$/, '')) + + # Format Expiry + reFormatExpiry = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = $.payment.formatExpiry(value) + safeVal(value, $target) + + formatExpiry = (e) -> + # Only format if input is a number + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + val = $target.val() + digit + + if /^\d$/.test(val) and val not in ['0', '1'] + e.preventDefault() + setTimeout -> $target.val("0#{val} / ") + + else if /^\d\d$/.test(val) + e.preventDefault() + setTimeout -> + # Split for months where we have the second digit > 2 (past 12) and turn + # that into (m1)(m2) => 0(m1) / (m2) + m1 = parseInt(val[0], 10) + m2 = parseInt(val[1], 10) + if m2 > 2 and m1 != 0 + $target.val("0#{m1} / #{m2}") + else + $target.val("#{val} / ") + + formatForwardExpiry = (e) -> + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + val = $target.val() + + if /^\d\d$/.test(val) + $target.val("#{val} / ") + + formatForwardSlash = (e) -> + which = String.fromCharCode(e.which) + return unless which is '/' or which is ' ' + + $target = $(e.currentTarget) + val = $target.val() + + if /^\d$/.test(val) and val isnt '0' + $target.val("0#{val} / ") + + formatBackExpiry = (e) -> + $target = $(e.currentTarget) + value = $target.val() + + # Return unless backspacing + return unless e.which is 8 + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + # Remove the trailing space + last digit + if /\d\s\/\s$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d\s\/\s$/, '')) + + parseExpiry = (value) -> + if value? + dateAsObj = ccUtils.parseExpiry(value); + expiry = new Date dateAsObj.year, dateAsObj.month - 1 + return $filter('date')(expiry, 'MM/yyyy') + + # Format CVC + reFormatCVC = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = value.replace(/\D/g, '')[0...4] + safeVal(value, $target) + + # Restrictions + restrictNumeric = (e) -> + # Key event is for a browser shortcut + return true if e.metaKey or e.ctrlKey + + # If keycode is a space + return false if e.which is 32 + + # If keycode is a special char (WebKit) + return true if e.which is 0 + + # If char is a special char (Firefox) + return true if e.which < 33 + + input = String.fromCharCode(e.which) + + # Char is a number or a space + !!/[\d\s]/.test(input) + + restrictCardNumber = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + # Restrict number of digits + value = ($target.val() + digit).replace(/\D/g, '') + card = cardFromNumber(value) + + if card + value.length <= card.length[card.length.length - 1] + else + # All other cards are 16 digits long + value.length <= 16 + + restrictExpiry = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + value = $target.val() + digit + value = value.replace(/\D/g, '') + + return false if value.length > 6 + + restrictCVC = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + val = $target.val() + digit + val.length <= 4 + + setCardType = (e) -> + $target = $(e.currentTarget) + val = $target.val() + cardType = $.payment.cardType(val) or 'unknown' + + unless $target.hasClass(cardType) + allTypes = (card.type for card in cards) + + $target.removeClass('unknown') + $target.removeClass(allTypes.join(' ')) + + $target.addClass(cardType) + $target.toggleClass('identified', cardType isnt 'unknown') + $target.trigger('payment.cardType', cardType) + + return { + hasTextSelected + replaceFullWidthChars + reFormatNumeric + reFormatCardNumber + formatCardNumber + formatBackCardNumber + reFormatExpiry + formatExpiry + formatForwardExpiry + formatForwardSlash + formatBackExpiry + parseExpiry + reFormatCVC + restrictNumeric + restrictCardNumber + restrictExpiry + restrictCVC + setCardType + } + + App.directive 'ccFormatExpiry', (ccFormat) -> + restrict: 'A' + require: 'ngModel' + link: (scope, el, attrs, ngModel) -> + el.on 'keypress', ccFormat.restrictExpiry + el.on 'keypress', ccFormat.formatExpiry + el.on 'keypress', ccFormat.formatForwardSlash + el.on 'keypress', ccFormat.formatForwardExpiry + el.on 'keydown', ccFormat.formatBackExpiry + + ngModel.$parsers.push ccFormat.parseExpiry + ngModel.$formatters.push ccFormat.parseExpiry + + + \ No newline at end of file From 4814fc2606b0fea6de44e728ea913154fc7e8a9b Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 17 Aug 2016 17:34:17 +0100 Subject: [PATCH 03/27] Simple layout for subscription form. --- services/web/app/views/subscriptions/new.jade | 56 ++++++++++++++++++- services/web/public/coffee/main.coffee | 1 + .../coffee/main/new-subscription.coffee | 5 +- services/web/public/stylesheets/style.less | 1 + 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 7613ec463e..d62e8e0de2 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -176,7 +176,54 @@ block content div.pull-right strong {{price.currency.symbol}}{{price.next.total}} .col-md-12(sixpack-when="simple") - span Lorem ipsum! + form(ng-show="planName") + .row + .col-md-12 + .form-group + .row + .col-md-6 + label.radio-inline + input.paymentTypeOption(type="radio", value="credit_card", ng-model="paymentMethod") + i.fa.fa-cc-mastercard.fa-3x + span   + i.fa.fa-cc-visa.fa-3x + .col-md-6 + label.radio-inline + input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") + i.fa.fa-cc-paypal.fa-3x + //- TODO Validation messages + .form-group + label(for="card-name") Name on card + input#card-name.form-control(type="text") + .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") + label(for="card-no") Card number + input#card-no.form-control(type="credit card", ng-model='data.number', ng-blur="validateCardNumber()") + .row + .col-xs-6 + label Valid thru {{ data.mmYY }} + input.form-control( + type="text" + ng-model="data.mmYY" + cc-format-expiry + ) + .col-xs-6 + .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") + label Security code + input.form-control(type="cvc", ng-model='data.cvv', ng-blur="validateCvv()") + .form-control-feedback + a.form-helper( + href + tooltip-template="'cvv-tooltip-tpl.html'" + tooltip-trigger="mouseenter" + tooltip-append-to-body="true" + ) ? + .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") + label(for="country") Country + select#country.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) + mixin countries_options() + .row + .col-xs-12 + button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") #{translate("upgrade_now")} span(sixpack-switch="payment-left-menu-bottom") @@ -272,6 +319,13 @@ block content script(type="text/javascript"). ga('send', 'event', 'pageview', 'payment_form', "#{plan_code}") + script( + type="text/ng-template" + id="cvv-tooltip-tpl.html" + ) + p For #[strong Visa, MasterCard and Discover], the #[strong 3 digits] on the #[strong back] of your card. + p For #[strong American Express], the #[strong 4 digits] on the #[strong front] of your card. + mixin countries_options() option(value='', disabled, selected) #{translate("country")} option(value='-') -------------- diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index d85d89cfe8..c723031016 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -26,6 +26,7 @@ define [ "directives/onEnter" "directives/selectAll" "directives/maxHeight" + "directives/creditCards" "services/queued-http" "filters/formatDate" "__MAIN_CLIENTSIDE_INCLUDES__" diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index c153392c15..47c1c28a34 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -1,5 +1,6 @@ define [ - "base" + "base", + "directives/creditCards" ], (App)-> App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking)-> @@ -14,6 +15,7 @@ define [ $scope.paymentMethod = "credit_card" + $scope.data = number: "" month: "" @@ -28,6 +30,7 @@ define [ city:"" country:window.countryCode coupon: window.couponCode + mmYY: "" $scope.validation = diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less index b02891425c..2abb57388f 100755 --- a/services/web/public/stylesheets/style.less +++ b/services/web/public/stylesheets/style.less @@ -75,6 +75,7 @@ @import "app/wiki.less"; @import "app/translations.less"; @import "app/contact-us.less"; +@import "app/subscription.less"; @import "app/sprites.less"; @import "../js/libs/pdfListView/TextLayer.css"; From 1af5017e49e2d2cc0c076aee183d4e0e013666de Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 10:39:55 +0100 Subject: [PATCH 04/27] Better integration with Angular models. --- .../web/public/coffee/directives/creditCards.coffee | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee index ae9df139a9..70b485252a 100644 --- a/services/web/public/coffee/directives/creditCards.coffee +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -90,6 +90,8 @@ define [ month = parseInt(month, 10) year = parseInt(year, 10) + return unless !isNaN(month) and !isNaN(year) + month: month, year: year return { @@ -271,8 +273,14 @@ define [ parseExpiry = (value) -> if value? - dateAsObj = ccUtils.parseExpiry(value); + dateAsObj = ccUtils.parseExpiry(value) + + console.log dateAsObj + + return unless dateAsObj? + expiry = new Date dateAsObj.year, dateAsObj.month - 1 + return $filter('date')(expiry, 'MM/yyyy') # Format CVC From 097df3a771d37902ff26cfa227f23f985be7ba17 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 16:55:54 +0100 Subject: [PATCH 05/27] Minor fixes. --- .../coffee/directives/creditCards.coffee | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee index 70b485252a..26c8dc1122 100644 --- a/services/web/public/coffee/directives/creditCards.coffee +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -137,7 +137,6 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '') - safeVal(value, $target) # Format Card Number reFormatCardNumber = (e) -> @@ -146,7 +145,6 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = $.payment.formatCardNumber(value) - #safeVal(value, $target) formatCardNumber = (e) -> # Only format if input is a number @@ -155,7 +153,7 @@ define [ $target = $(e.currentTarget) value = $target.val() - card = ccUtils.cardFromNumber(value + digit) + card = ccUtils.fromNumber(value + digit) length = (value.replace(/\D/g, '') + digit).length upperLength = 16 @@ -202,6 +200,26 @@ define [ e.preventDefault() setTimeout -> $target.val(value.replace(/\d$/, '')) + getFormattedCardNumber = (num) -> + num = num.replace(/\D/g, '') + card = ccUtils.fromNumber(num) + return num unless card + + upperLength = card.length[card.length.length - 1] + num = num[0...upperLength] + + if card.format.global + num.match(card.format)?.join(' ') + else + groups = card.format.exec(num) + return unless groups? + groups.shift() + groups = $.grep(groups, (n) -> n) # Filter empty groups + groups.join(' ') + + parseCardNumber = (value) -> + if value? then value.replace(/\s/g, '') else value + # Format Expiry reFormatExpiry = (e) -> $target = $(e.currentTarget) @@ -209,7 +227,6 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = $.payment.formatExpiry(value) - safeVal(value, $target) formatExpiry = (e) -> # Only format if input is a number @@ -275,8 +292,6 @@ define [ if value? dateAsObj = ccUtils.parseExpiry(value) - console.log dateAsObj - return unless dateAsObj? expiry = new Date dateAsObj.year, dateAsObj.month - 1 @@ -290,7 +305,6 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '')[0...4] - safeVal(value, $target) # Restrictions restrictNumeric = (e) -> @@ -320,7 +334,7 @@ define [ # Restrict number of digits value = ($target.val() + digit).replace(/\D/g, '') - card = cardFromNumber(value) + card = ccUtils.fromNumber(value) if card value.length <= card.length[card.length.length - 1] @@ -372,6 +386,8 @@ define [ reFormatCardNumber formatCardNumber formatBackCardNumber + getFormattedCardNumber + parseCardNumber reFormatExpiry formatExpiry formatForwardExpiry @@ -386,18 +402,41 @@ define [ setCardType } - App.directive 'ccFormatExpiry', (ccFormat) -> - restrict: 'A' - require: 'ngModel' + App.directive "ccFormatExpiry", (ccFormat) -> + restrict: "A" + require: "ngModel" link: (scope, el, attrs, ngModel) -> - el.on 'keypress', ccFormat.restrictExpiry - el.on 'keypress', ccFormat.formatExpiry - el.on 'keypress', ccFormat.formatForwardSlash - el.on 'keypress', ccFormat.formatForwardExpiry - el.on 'keydown', ccFormat.formatBackExpiry + el.on "keypress", ccFormat.restrictExpiry + el.on "keypress", ccFormat.formatExpiry + el.on "keypress", ccFormat.formatForwardSlash + el.on "keypress", ccFormat.formatForwardExpiry + el.on "keydown", ccFormat.formatBackExpiry ngModel.$parsers.push ccFormat.parseExpiry ngModel.$formatters.push ccFormat.parseExpiry + App.directive "ccFormatCardNumber", (ccFormat) -> + restrict: "A" + require: "ngModel" + link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictCardNumber + el.on "keypress", ccFormat.formatCardNumber + el.on "keydown", ccFormat.formatBackCardNumber + el.on "paste", ccFormat.reFormatCardNumber + + ngModel.$parsers.push ccFormat.parseCardNumber + ngModel.$formatters.push ccFormat.getFormattedCardNumber + + App.directive "ccFormatSecCode", (ccFormat) -> + restrict: "A" + require: "ngModel" + link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictNumeric + el.on "keypress", ccFormat.restrictCVC + el.on "paste", ccFormat.reFormatCVC + el.on "change", ccFormat.reFormatCVC + el.on "input", ccFormat.reFormatCVC + + \ No newline at end of file From 43b832965fbb84a5a1479101dc1b149f373c3530 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 16:56:23 +0100 Subject: [PATCH 06/27] Layout and styling for simple subscription form. --- services/web/app/views/subscriptions/new.jade | 156 ++++++++++-------- .../public/stylesheets/app/subscription.less | 62 +++++++ 2 files changed, 150 insertions(+), 68 deletions(-) create mode 100644 services/web/public/stylesheets/app/subscription.less diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index d62e8e0de2..5bbda1aa95 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -176,54 +176,73 @@ block content div.pull-right strong {{price.currency.symbol}}{{price.next.total}} .col-md-12(sixpack-when="simple") - form(ng-show="planName") - .row - .col-md-12 - .form-group - .row - .col-md-6 - label.radio-inline - input.paymentTypeOption(type="radio", value="credit_card", ng-model="paymentMethod") - i.fa.fa-cc-mastercard.fa-3x - span   - i.fa.fa-cc-visa.fa-3x - .col-md-6 - label.radio-inline - input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") - i.fa.fa-cc-paypal.fa-3x + form(ng-if="planName") + div.payment-method-toggle + a.payment-method-toggle-switch( + href + ng-click="paymentMethod = 'credit_card'" + ng-class="paymentMethod === 'credit_card' ? 'payment-method-toggle-switch-selected' : ''" + ) + i.fa.fa-cc-mastercard.fa-2x + span   + i.fa.fa-cc-visa.fa-2x + span   + i.fa.fa-cc-amex.fa-2x + a.payment-method-toggle-switch( + href + ng-click="paymentMethod = 'paypal'" + ng-class="paymentMethod === 'paypal' ? 'payment-method-toggle-switch-selected' : ''" + ) + i.fa.fa-cc-paypal.fa-2x //- TODO Validation messages - .form-group - label(for="card-name") Name on card - input#card-name.form-control(type="text") - .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") - label(for="card-no") Card number - input#card-no.form-control(type="credit card", ng-model='data.number', ng-blur="validateCardNumber()") - .row - .col-xs-6 - label Valid thru {{ data.mmYY }} - input.form-control( + div(ng-if="paymentMethod === 'credit_card'") + .form-group + label(for="card-name") Name on card + input#card-name.form-control(type="text") + .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") + label(for="card-no") Card number + input#card-no.form-control( type="text" - ng-model="data.mmYY" - cc-format-expiry - ) - .col-xs-6 - .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") - label Security code - input.form-control(type="cvc", ng-model='data.cvv', ng-blur="validateCvv()") - .form-control-feedback - a.form-helper( - href - tooltip-template="'cvv-tooltip-tpl.html'" - tooltip-trigger="mouseenter" - tooltip-append-to-body="true" - ) ? - .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") - label(for="country") Country - select#country.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) - mixin countries_options() - .row - .col-xs-12 - button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") #{translate("upgrade_now")} + ng-model="data.number" + ng-blur="validateCardNumber()" + cc-format-card-number) + .row + .col-xs-6 + label Expiry date + input.form-control( + type="text" + ng-model="data.mmYY" + placeholder="MM // YY" + cc-format-expiry + ) + .col-xs-6 + .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") + label Security code + input.form-control( + type="text" + ng-model="data.cvv" + ng-blur="validateCvv()" + cc-format-sec-code) + .form-control-feedback + a.form-helper( + href + tabindex="-1" + tooltip-template="'cvv-tooltip-tpl.html'" + tooltip-trigger="mouseenter" + tooltip-append-to-body="true" + ) ? + .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") + label(for="country") Country + select#country.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) + mixin countries_options() + .row.payment-form-submit + .col-xs-12 + button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") Upgrade now, pay after 7 days + div(ng-if="paymentMethod === 'paypal'") + p Click the button below to login with PayPal and upgrade. + .row.payment-form-submit + .col-xs-12 + button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") Continue span(sixpack-switch="payment-left-menu-bottom") @@ -279,32 +298,33 @@ block content .paymentPageFeatures .page-header h2 #{translate("features")} - h3 - i.fa.fa-check   - | #{translate("unlimited_projects")} + h3.feature + .features-check: i.fa.fa-check + .features-copy #{translate("unlimited_projects")} - h3 - i.fa.fa-check   - if plan.features.collaborators == -1 - - var collaboratorCount = 'Unlimited' - else - - var collaboratorCount = plan.features.collaborators - | #{translate("collabs_per_proj", {collabcount:collaboratorCount})} + h3.feature + .features-check: i.fa.fa-check + .features-copy + if plan.features.collaborators == -1 + - var collaboratorCount = 'Unlimited' + else + - var collaboratorCount = plan.features.collaborators + | #{translate("collabs_per_proj", {collabcount:collaboratorCount})} - h3 - i.fa.fa-check   - | #{translate("full_doc_history")} + h3.feature + .features-check: i.fa.fa-check + .features-copy #{translate("full_doc_history")} - h3 - i.fa.fa-check   - | #{translate("sync_to_dropbox")} + h3.feature + .features-check: i.fa.fa-check + .features-copy #{translate("sync_to_dropbox")} - h3 - i.fa.fa-check   - | #{translate("sync_to_github")} - h3 - i.fa.fa-check   - | #{translate("Compile Larger Projects")} + h3.feature + .features-check: i.fa.fa-check + .features-copy #{translate("sync_to_github")} + h3.feature + .features-check: i.fa.fa-check + .features-copy #{translate("compile_larger_projects")} hr h2.text-center 30 Day Guarantee diff --git a/services/web/public/stylesheets/app/subscription.less b/services/web/public/stylesheets/app/subscription.less new file mode 100644 index 0000000000..1bafe10700 --- /dev/null +++ b/services/web/public/stylesheets/app/subscription.less @@ -0,0 +1,62 @@ +.form-helper { + display: inline-block; + width: 1.3em; + height: 1.3em; + line-height: 1.3; + vertical-align: initial; + background-color: @gray; + color: #FFF; + font-weight: bolder; + border-radius: 50%; + + &:hover, + &:focus { + color: #FFF; + text-decoration: none; + } +} + +.payment-form { + &-submit { + padding-top: (@line-height-computed / 2); + } +} + +.payment-method-toggle { + margin-bottom: (@line-height-computed / 2); + + &-switch { + display: inline-block; + width: 50%; + text-align: center; + border: solid 1px @gray-lighter; + border-radius: @border-radius-large 0 0 @border-radius-large; + padding: (@line-height-computed / 2); + color: @gray; + + &:hover, + &:focus { + color: @gray; + text-decoration: none; + } + + &:hover { + color: @gray-dark; + } + + & + & { + border-left-width: 0; + border-radius: 0 @border-radius-large @border-radius-large 0; + } + + &-selected { + color: @link-color; + box-shadow: inset 0 -2px 0 0; + + &:hover, + &:focus { + color: @link-color; + } + } + } +} \ No newline at end of file From 82a6cd82a6a1cb81af532d09c04a32a82b162f2b Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 16:56:36 +0100 Subject: [PATCH 07/27] Minor tweaks in the features list. --- .../web/public/stylesheets/app/plans.less | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/services/web/public/stylesheets/app/plans.less b/services/web/public/stylesheets/app/plans.less index c375052394..b830db9b23 100644 --- a/services/web/public/stylesheets/app/plans.less +++ b/services/web/public/stylesheets/app/plans.less @@ -89,8 +89,27 @@ .small { font-size: 12px; } + } +.feature { + margin-top: (@line-height-computed / 2); + margin-bottom: (@line-height-computed / 1.5); +} + +.features-check, +.features-copy { + display: inline-block; + width: 12%; + line-height: 1.4; + vertical-align: top; +} + +.features-copy { + width: 88%; +} + + .plansPageStudentLink { margin-left: 20px; margin-top: 20px; From 26e21732f88bc0a84763bbafd44fb5910bf8ded8 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 17:41:55 +0100 Subject: [PATCH 08/27] Added validations + minor fixes. --- services/web/app/views/subscriptions/new.jade | 27 +++++++++++++------ .../coffee/directives/creditCards.coffee | 27 +++++++++++++++++-- .../coffee/main/new-subscription.coffee | 11 +++++--- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 5bbda1aa95..8e8dcb8b23 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -175,6 +175,7 @@ block content div(ng-if="price.next.tax!='0.00'").pull-right {{price.currency.symbol}}{{price.next.tax}} div.pull-right strong {{price.currency.symbol}}{{price.next.total}} + .col-md-12(sixpack-when="simple") form(ng-if="planName") div.payment-method-toggle @@ -196,10 +197,18 @@ block content i.fa.fa-cc-paypal.fa-2x //- TODO Validation messages div(ng-if="paymentMethod === 'credit_card'") + ol + li validation.correctCardNumber : {{ validation.correctCardNumber }} + li validation.correctExpiry : {{ validation.correctExpiry }} + li validation.correctCvv : {{ validation.correctCvv }} + li data.month : {{ data.month }} + li data.year : {{ data.year }} + li data.mmYY : {{ data.mmYY }} + .form-group label(for="card-name") Name on card input#card-name.form-control(type="text") - .form-group(ng-class="validation.number == false || validation.errorFields.number ? 'has-error' : ''") + .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number ? 'has-error' : ''") label(for="card-no") Card number input#card-no.form-control( type="text" @@ -208,13 +217,15 @@ block content cc-format-card-number) .row .col-xs-6 - label Expiry date - input.form-control( - type="text" - ng-model="data.mmYY" - placeholder="MM // YY" - cc-format-expiry - ) + .form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry ? 'has-error' : ''") + label Expiry date + input.form-control( + type="text" + ng-model="data.mmYY" + placeholder="MM / YY" + ng-blur="validateExpiry()" + cc-format-expiry + ) .col-xs-6 .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") label Security code diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee index 26c8dc1122..bfac125f6a 100644 --- a/services/web/public/coffee/directives/creditCards.coffee +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -78,6 +78,27 @@ define [ cardFromType = (type) -> return card for card in cards when card.type is type + cardType = (num) -> + return null unless num + cardFromNumber(num)?.type or null + + formatCardNumber = (num) -> + num = num.replace(/\D/g, '') + card = cardFromNumber(num) + return num unless card + + upperLength = card.length[card.length.length - 1] + num = num[0...upperLength] + + if card.format.global + num.match(card.format)?.join(' ') + else + groups = card.format.exec(num) + return unless groups? + groups.shift() + groups = $.grep(groups, (n) -> n) # Filter empty groups + groups.join(' ') + parseExpiry = (value = "") -> [month, year] = value.split(/[\s\/]+/, 2) @@ -97,6 +118,8 @@ define [ return { fromNumber: cardFromNumber fromType: cardFromType + cardType: cardType + formatCardNumber: formatCardNumber defaultFormat: defaultFormat defaultInputFormat: defaultInputFormat parseExpiry: parseExpiry @@ -144,7 +167,7 @@ define [ setTimeout -> value = $target.val() value = replaceFullWidthChars(value) - value = $.payment.formatCardNumber(value) + value = ccUtils.formatCardNumber(value) formatCardNumber = (e) -> # Only format if input is a number @@ -367,7 +390,7 @@ define [ setCardType = (e) -> $target = $(e.currentTarget) val = $target.val() - cardType = $.payment.cardType(val) or 'unknown' + cardType = ccUtils.cardType(val) or 'unknown' unless $target.hasClass(cardType) allTypes = (card.type for card in cards) diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 47c1c28a34..0d3b9f1728 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -3,7 +3,7 @@ define [ "directives/creditCards" ], (App)-> - App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking)-> + App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)-> throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined" $scope.currencyCode = MultiCurrencyPricing.currencyCode @@ -32,11 +32,16 @@ define [ coupon: window.couponCode mmYY: "" - + $scope.$watch 'data.mmYY', (newVal) -> + parsedDateObj = ccUtils.parseExpiry newVal + if parsedDateObj? + $scope.data.month = parsedDateObj.month + $scope.data.year = parsedDateObj.year + $scope.validation = correctCardNumber : true correctExpiry: true - correctCvv:true + correctCvv: true $scope.processing = false From 67290fd6a7c0cf733d1ac7d9b4ea78430e05702a Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 17:56:53 +0100 Subject: [PATCH 09/27] Fix formatting on change and paste events. --- .../coffee/directives/creditCards.coffee | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee index bfac125f6a..3ee89a610a 100644 --- a/services/web/public/coffee/directives/creditCards.coffee +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -99,6 +99,30 @@ define [ groups = $.grep(groups, (n) -> n) # Filter empty groups groups.join(' ') + formatExpiry = (expiry) -> + parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/) + return '' unless parts + + mon = parts[1] || '' + sep = parts[2] || '' + year = parts[3] || '' + + if year.length > 0 + sep = ' / ' + + else if sep is ' /' + mon = mon.substring(0, 1) + sep = '' + + else if mon.length == 2 or sep.length > 0 + sep = ' / ' + + else if mon.length == 1 and mon not in ['0', '1'] + mon = "0#{mon}" + sep = ' / ' + + return mon + sep + year + parseExpiry = (value = "") -> [month, year] = value.split(/[\s\/]+/, 2) @@ -119,6 +143,7 @@ define [ fromNumber: cardFromNumber fromType: cardFromType cardType: cardType + formatExpiry: formatExpiry formatCardNumber: formatCardNumber defaultFormat: defaultFormat defaultInputFormat: defaultInputFormat @@ -137,6 +162,45 @@ define [ false + safeVal = (value, $target) -> + try + cursor = $target.prop('selectionStart') + catch error + cursor = null + + last = $target.val() + $target.val(value) + + if cursor != null && $target.is(":focus") + cursor = value.length if cursor is last.length + + # This hack looks for scenarios where we are changing an input's value such + # that "X| " is replaced with " |X" (where "|" is the cursor). In those + # scenarios, we want " X|". + # + # For example: + # 1. Input field has value "4444| " + # 2. User types "1" + # 3. Input field has value "44441| " + # 4. Reformatter changes it to "4444 |1" + # 5. By incrementing the cursor, we make it "4444 1|" + # + # This is awful, and ideally doesn't go here, but given the current design + # of the system there does not appear to be a better solution. + # + # Note that we can't just detect when the cursor-1 is " ", because that + # would incorrectly increment the cursor when backspacing, e.g. pressing + # backspace in this scenario: "4444 1|234 5". + if last != value + prevPair = last[cursor-1..cursor] + currPair = value[cursor-1..cursor] + digit = value[cursor] + cursor = cursor + 1 if /\d/.test(digit) and + prevPair == "#{digit} " and currPair == " #{digit}" + + $target.prop('selectionStart', cursor) + $target.prop('selectionEnd', cursor) + # Replace Full-Width Chars replaceFullWidthChars = (str = '') -> fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' @@ -160,6 +224,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '') + safeVal(value, $target) # Format Card Number reFormatCardNumber = (e) -> @@ -168,6 +233,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = ccUtils.formatCardNumber(value) + safeVal(value, $target) formatCardNumber = (e) -> # Only format if input is a number @@ -249,7 +315,9 @@ define [ setTimeout -> value = $target.val() value = replaceFullWidthChars(value) - value = $.payment.formatExpiry(value) + value = ccUtils.formatExpiry(value) + safeVal(value, $target) + formatExpiry = (e) -> # Only format if input is a number @@ -328,6 +396,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '')[0...4] + safeVal(value, $target) # Restrictions restrictNumeric = (e) -> @@ -429,11 +498,15 @@ define [ restrict: "A" require: "ngModel" link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictNumeric el.on "keypress", ccFormat.restrictExpiry el.on "keypress", ccFormat.formatExpiry el.on "keypress", ccFormat.formatForwardSlash el.on "keypress", ccFormat.formatForwardExpiry el.on "keydown", ccFormat.formatBackExpiry + el.on "change", ccFormat.reFormatExpiry + el.on "input", ccFormat.reFormatExpiry + el.on "paste", ccFormat.reFormatExpiry ngModel.$parsers.push ccFormat.parseExpiry ngModel.$formatters.push ccFormat.parseExpiry @@ -442,6 +515,7 @@ define [ restrict: "A" require: "ngModel" link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictNumeric el.on "keypress", ccFormat.restrictCardNumber el.on "keypress", ccFormat.formatCardNumber el.on "keydown", ccFormat.formatBackCardNumber From 3d36dc7d6c6cad7e5365a5f944288586f93c6103 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 19 Aug 2016 11:05:35 +0100 Subject: [PATCH 10/27] mvp for not using cdn when blocked --- .../app/coffee/infrastructure/ExpressLocals.coffee | 9 +++++++-- services/web/app/views/layout.jade | 11 ++++++++++- services/web/config/settings.defaults.coffee | 6 +++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index ee7418dacc..155b6ac3c0 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -61,11 +61,16 @@ module.exports = (app, webRouter, apiRouter)-> webRouter.use (req, res, next)-> + cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked + + if cdnBlocked and !req.session.cdnBlocked? + req.session.cdnBlocked = true + isDark = req.headers?.host?.slice(0,4)?.toLowerCase() == "dark" isSmoke = req.headers?.host?.slice(0,5)?.toLowerCase() == "smoke" isLive = !isDark and !isSmoke - - if cdnAvailable and isLive + + if cdnAvailable and isLive and !cdnBlocked staticFilesBase = Settings.cdn?.web?.host else if darkCdnAvailable and isDark staticFilesBase = Settings.cdn?.web?.darkHost diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 3c781a4b9f..f1bfeedfee 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -8,6 +8,7 @@ html(itemscope, itemtype='http://schema.org/Product') window.similarproducts = true style [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {display: none !important; display: none; } + -if (typeof(gaExperiments) != "undefined") |!{gaExperiments} @@ -51,8 +52,16 @@ html(itemscope, itemtype='http://schema.org/Product') window.csrfToken = "#{csrfToken}"; block scripts - script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false})) script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false})) + script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false})) + script(type="text/javascript"). + var noCdnKey = "nocdn=true" + var cdnBlocked = typeof jQuery === 'undefined' + var noCdnAlreadyInUrl = window.location.href.indexOf(noCdnKey) != -1 //prevent loops + if (cdnBlocked && !noCdnAlreadyInUrl) { + window.location.search += '&'+noCdnKey; + } + script. window.sharelatex = { siteUrl: '#{settings.siteUrl}', diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index a326c3b07d..56f3ee2b81 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -114,9 +114,9 @@ module.exports = settings = showSocialButtons: false showComments: false - # cdn: - # web: - # host:"http://cdn.sharelatex.dev:3000" + cdn: + web: + host:"http://nowhere.sharelatex.dev" # darkHost:"http://cdn.sharelatex.dev:3000" # Where your instance of ShareLaTeX can be found publically. Used in emails From d8e7bacec4f82ea46912bf63df010ef19d716304 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 19 Aug 2016 11:53:40 +0100 Subject: [PATCH 11/27] added logging in --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 155b6ac3c0..60f1b682e5 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -64,12 +64,14 @@ module.exports = (app, webRouter, apiRouter)-> cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked if cdnBlocked and !req.session.cdnBlocked? + ip = req.ip || req.socket?.socket?.remoteAddress || req.socket?.remoteAddress + logger.log user_id:req?.session?.user?._id, ip:ip, "cdnBlocked for user, not using it" req.session.cdnBlocked = true isDark = req.headers?.host?.slice(0,4)?.toLowerCase() == "dark" isSmoke = req.headers?.host?.slice(0,5)?.toLowerCase() == "smoke" isLive = !isDark and !isSmoke - + if cdnAvailable and isLive and !cdnBlocked staticFilesBase = Settings.cdn?.web?.host else if darkCdnAvailable and isDark From 452abe94be3137e78acd3422756e7463dfcab326 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 19 Aug 2016 14:51:07 +0100 Subject: [PATCH 12/27] Add coupon code, VAT and pricing breakdown. Styling adjustments. --- services/web/app/views/subscriptions/new.jade | 74 ++++++++++++++----- .../coffee/main/new-subscription.coffee | 4 + .../public/stylesheets/app/subscription.less | 11 ++- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 8e8dcb8b23..b3ffdbc8b1 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -69,8 +69,6 @@ block content input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") i.fa.fa-cc-paypal.fa-3x - - .alert.alert-warning.small(ng-show="genericError") strong {{genericError}} @@ -195,16 +193,18 @@ block content ng-class="paymentMethod === 'paypal' ? 'payment-method-toggle-switch-selected' : ''" ) i.fa.fa-cc-paypal.fa-2x - //- TODO Validation messages - div(ng-if="paymentMethod === 'credit_card'") - ol - li validation.correctCardNumber : {{ validation.correctCardNumber }} - li validation.correctExpiry : {{ validation.correctExpiry }} - li validation.correctCvv : {{ validation.correctCvv }} - li data.month : {{ data.month }} - li data.year : {{ data.year }} - li data.mmYY : {{ data.mmYY }} + + .alert.alert-warning.small(ng-show="genericError") + strong {{genericError}} + div(ng-if="paymentMethod === 'credit_card'") + //- ol + //- li validation.correctCardNumber : {{ validation.correctCardNumber }} + //- li validation.correctExpiry : {{ validation.correctExpiry }} + //- li validation.correctCvv : {{ validation.correctCvv }} + //- li data.month : {{ data.month }} + //- li data.year : {{ data.year }} + //- li data.mmYY : {{ data.mmYY }} .form-group label(for="card-name") Name on card input#card-name.form-control(type="text") @@ -244,16 +244,50 @@ block content ) ? .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") label(for="country") Country - select#country.form-control(data-recurly="country", ng-model="data.country", ng-change="updateCountry()", required) + select#country.form-control( + data-recurly="country" + ng-model="data.country" + ng-change="updateCountry()" + required + ) mixin countries_options() - .row.payment-form-submit - .col-xs-12 - button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") Upgrade now, pay after 7 days - div(ng-if="paymentMethod === 'paypal'") - p Click the button below to login with PayPal and upgrade. - .row.payment-form-submit - .col-xs-12 - button.btn.btn-success.btn-block(ng-click="submit()", ng-disabled="processing", sixpack-convert="payment-left-menu-bottom") Continue + + div + if (showVatField) + .form-group + label(for="vat-no") #{translate('vat_number')} + input#vat-no.form-control( + type='text' + ng-blur="applyVatNumber()" + ng-model="data.vat_number" + ) + if (showCouponField) + .form-group + label(for="coupon-code") #{translate('coupon_code')} + input#coupon-code.form-control( + type='text' + ng-blur="applyCoupon()" + ng-model="data.coupon" + ) + + //- PRicing stuff + + p(ng-if="paymentMethod === 'paypal'") To upgrade, click on the button below and log on to PayPal using your email and password. + div.price-breakdown(ng-if="price.next.tax !== '0.00'") + hr.thin + span Total: + strong {{price.currency.symbol}}{{price.next.total}} + span ({{price.currency.symbol}}{{price.next.subtotal}} + {{price.currency.symbol}}{{price.next.tax}} tax) + span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")} + span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")} + hr.thin + + div.payment-submit + button.btn.btn-success.btn-block( + ng-click="submit()" + ng-disabled="processing" + sixpack-convert="payment-left-menu-bottom" + ) {{ paymentMethod === 'credit_card' ? 'Upgrade now, pay after 7 days' : 'Continue' }} span(sixpack-switch="payment-left-menu-bottom") diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 0d3b9f1728..ae81d73bb0 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -65,6 +65,7 @@ define [ $scope.price = pricing.price $scope.trialLength = pricing.items.plan.trial?.length $scope.monthlyBilling = pricing.items.plan.period.length == 1 + if pricing.items?.coupon?.discount?.type == "percent" basePrice = parseInt(pricing.price.base.plan.unit) $scope.normalPrice = basePrice @@ -100,6 +101,7 @@ define [ $scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv) $scope.updateCountry = -> + console.log 'update country' pricing.address({country:$scope.data.country}).done() $scope.changePaymentMethod = (paymentMethod)-> @@ -140,6 +142,8 @@ define [ isPaypal : postData.subscriptionDetails.isPaypal } + sixpack.convert "subscription-form" + $http.post("/user/subscription/create", postData) .success (data, status, headers)-> sixpack.convert "in-editor-free-trial-plan", pricing.items.plan.code, (err)-> diff --git a/services/web/public/stylesheets/app/subscription.less b/services/web/public/stylesheets/app/subscription.less index 1bafe10700..5e7656adc7 100644 --- a/services/web/public/stylesheets/app/subscription.less +++ b/services/web/public/stylesheets/app/subscription.less @@ -16,10 +16,13 @@ } } -.payment-form { - &-submit { - padding-top: (@line-height-computed / 2); - } +.price-breakdown { + text-align: center; + margin-bottom: -10px; +} + +.payment-submit { + padding-top: (@line-height-computed / 2); } .payment-method-toggle { From 6a1f0de91218eafa9f0c7fe775de73731294f31c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 19 Aug 2016 15:12:05 +0100 Subject: [PATCH 13/27] Add i18n keys. --- services/web/app/views/subscriptions/new.jade | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index b3ffdbc8b1..03aa58c052 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -206,10 +206,10 @@ block content //- li data.year : {{ data.year }} //- li data.mmYY : {{ data.mmYY }} .form-group - label(for="card-name") Name on card + label(for="card-name") #{translate("name_on_card")} input#card-name.form-control(type="text") .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number ? 'has-error' : ''") - label(for="card-no") Card number + label(for="card-no") #{translate("credit_card_number")} input#card-no.form-control( type="text" ng-model="data.number" @@ -218,7 +218,7 @@ block content .row .col-xs-6 .form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry ? 'has-error' : ''") - label Expiry date + label #{translate("expiry")} input.form-control( type="text" ng-model="data.mmYY" @@ -228,7 +228,7 @@ block content ) .col-xs-6 .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") - label Security code + label #{translate("security_code")} input.form-control( type="text" ng-model="data.cvv" @@ -243,7 +243,7 @@ block content tooltip-append-to-body="true" ) ? .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") - label(for="country") Country + label(for="country") #{translate('country')} select#country.form-control( data-recurly="country" ng-model="data.country" @@ -270,16 +270,14 @@ block content ng-model="data.coupon" ) - //- PRicing stuff - - p(ng-if="paymentMethod === 'paypal'") To upgrade, click on the button below and log on to PayPal using your email and password. + p(ng-if="paymentMethod === 'paypal'") #{translate("paypal_upgrade")} div.price-breakdown(ng-if="price.next.tax !== '0.00'") hr.thin span Total: strong {{price.currency.symbol}}{{price.next.total}} span ({{price.currency.symbol}}{{price.next.subtotal}} + {{price.currency.symbol}}{{price.next.tax}} tax) - span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")} - span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")} + span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")} + span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")} hr.thin div.payment-submit @@ -287,7 +285,7 @@ block content ng-click="submit()" ng-disabled="processing" sixpack-convert="payment-left-menu-bottom" - ) {{ paymentMethod === 'credit_card' ? 'Upgrade now, pay after 7 days' : 'Continue' }} + ) {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} span(sixpack-switch="payment-left-menu-bottom") From 110820face184d07212f50cb5359df90da90ab91 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 19 Aug 2016 15:39:58 +0100 Subject: [PATCH 14/27] use url.resolve to build url for freegeoip lookups --- services/web/app/coffee/infrastructure/GeoIpLookup.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/infrastructure/GeoIpLookup.coffee b/services/web/app/coffee/infrastructure/GeoIpLookup.coffee index 5b64b8e6f4..88f3ed4bfa 100644 --- a/services/web/app/coffee/infrastructure/GeoIpLookup.coffee +++ b/services/web/app/coffee/infrastructure/GeoIpLookup.coffee @@ -2,6 +2,7 @@ request = require("request") settings = require("settings-sharelatex") _ = require("underscore") logger = require("logger-sharelatex") +URL = require("url") currencyMappings = { "GB":"GBP" @@ -31,7 +32,7 @@ module.exports = GeoIpLookup = return callback(e) ip = ip.trim().split(" ")[0] opts = - url: "#{settings.apis.geoIpLookup.url}/#{ip}" + url: URL.resolve(settings.apis.geoIpLookup.url,ip) timeout: 1000 json:true logger.log ip:ip, opts:opts, "getting geo ip details" From 53c6e48da98827c24917062052c7556586c68674 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 19 Aug 2016 16:03:15 +0100 Subject: [PATCH 15/27] Remove left-hand panel experiment. --- services/web/app/views/subscriptions/new.jade | 120 ++++++------------ 1 file changed, 36 insertions(+), 84 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 03aa58c052..94493c303d 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -287,96 +287,48 @@ block content sixpack-convert="payment-left-menu-bottom" ) {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} - span(sixpack-switch="payment-left-menu-bottom") - .col-md-3.col-md-pull-4(sixpack-default) - if showStudentPlan == 'true' - a.btn-primary.btn.plansPageStudentLink( - href, - ng-click="switchToStudent()" - ) #{translate("half_price_student")} + .col-md-3.col-md-pull-4 + if showStudentPlan == 'true' + a.btn-primary.btn.plansPageStudentLink( + href, + ng-click="switchToStudent()" + ) #{translate("half_price_student")} - .card.card-first - .paymentPageFeatures - h3 #{translate("unlimited_projects")} - p #{translate("create_unlimited_projects")} + .card.card-first + .paymentPageFeatures + h3 #{translate("unlimited_projects")} + p #{translate("create_unlimited_projects")} + + h3 + if plan.features.collaborators == -1 + - var collaboratorCount = 'Unlimited' + else + - var collaboratorCount = plan.features.collaborators + | #{translate("collabs_per_proj", {collabcount:collaboratorCount})} + p #{translate("work_on_single_version")}. #{translate("view_collab_edits")} in real time. - h3 - if plan.features.collaborators == -1 - - var collaboratorCount = 'Unlimited' - else - - var collaboratorCount = plan.features.collaborators - | #{translate("collabs_per_proj", {collabcount:collaboratorCount})} - p #{translate("work_on_single_version")}. #{translate("view_collab_edits")} in real time. - - h3 #{translate("full_doc_history")} - p #{translate("see_what_has_been")} - span.added #{translate("added")} - | #{translate("and")} - span.removed #{translate("removed")}. - | #{translate("restore_to_any_older_version")}. - - h3 #{translate("sync_to_dropbox")} - p - | #{translate("acces_work_from_anywhere")}. - | #{translate("work_offline_and_sync_with_dropbox")}. - - hr - - p.small.text-center We're confident that you'll love ShareLaTeX, but if not you can cancel anytime. We'll give you your money back, no questions asked, if you let us know within 30 days. - hr - span                   - a(href="https://www.positivessl.com" style="font-family: arial; font-size: 10px; color: #212121; text-decoration: none;") - img(src="https://www.positivessl.com/images-new/PositiveSSL_tl_trans.png" alt="SSL Certificate" title="SSL Certificate" border="0") - div(style="font-family: arial;font-weight:bold;font-size:15px;color:#86BEE0;") - a(href="https://www.positivessl.com" style="color:#86BEE0; text-decoration: none;") - .col-md-3.col-md-pull-4(sixpack-when="bolder") - if showStudentPlan == 'true' - a.btn-primary.btn.plansPageStudentLink( - href, - ng-click="switchToStudent()" - ) #{translate("half_price_student")} - - .card.card-first - .paymentPageFeatures - .page-header - h2 #{translate("features")} - h3.feature - .features-check: i.fa.fa-check - .features-copy #{translate("unlimited_projects")} + h3 #{translate("full_doc_history")} + p #{translate("see_what_has_been")} + span.added #{translate("added")} + | #{translate("and")} + span.removed #{translate("removed")}. + | #{translate("restore_to_any_older_version")}. - h3.feature - .features-check: i.fa.fa-check - .features-copy - if plan.features.collaborators == -1 - - var collaboratorCount = 'Unlimited' - else - - var collaboratorCount = plan.features.collaborators - | #{translate("collabs_per_proj", {collabcount:collaboratorCount})} + h3 #{translate("sync_to_dropbox")} + p + | #{translate("acces_work_from_anywhere")}. + | #{translate("work_offline_and_sync_with_dropbox")}. - h3.feature - .features-check: i.fa.fa-check - .features-copy #{translate("full_doc_history")} + hr - h3.feature - .features-check: i.fa.fa-check - .features-copy #{translate("sync_to_dropbox")} - - h3.feature - .features-check: i.fa.fa-check - .features-copy #{translate("sync_to_github")} - h3.feature - .features-check: i.fa.fa-check - .features-copy #{translate("compile_larger_projects")} - hr - - h2.text-center 30 Day Guarantee - hr - span                   - a(href="https://www.positivessl.com" style="font-family: arial; font-size: 10px; color: #212121; text-decoration: none;") - img(src="https://www.positivessl.com/images-new/PositiveSSL_tl_trans.png" alt="SSL Certificate" title="SSL Certificate" border="0") - div(style="font-family: arial;font-weight:bold;font-size:15px;color:#86BEE0;") - a(href="https://www.positivessl.com" style="color:#86BEE0; text-decoration: none;") + p.small.text-center We're confident that you'll love ShareLaTeX, but if not you can cancel anytime. We'll give you your money back, no questions asked, if you let us know within 30 days. + hr + span                   + a(href="https://www.positivessl.com" style="font-family: arial; font-size: 10px; color: #212121; text-decoration: none;") + img(src="https://www.positivessl.com/images-new/PositiveSSL_tl_trans.png" alt="SSL Certificate" title="SSL Certificate" border="0") + div(style="font-family: arial;font-weight:bold;font-size:15px;color:#86BEE0;") + a(href="https://www.positivessl.com" style="color:#86BEE0; text-decoration: none;") script(type="text/javascript"). From f4b8cc7fc813c0527d3bd2caef1826de8ee66c14 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 19 Aug 2016 16:46:44 +0100 Subject: [PATCH 16/27] Use first and last name instead of full name as input. --- services/web/app/views/subscriptions/new.jade | 24 +++++++++---------- .../coffee/main/new-subscription.coffee | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 94493c303d..852a2f5ab3 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -53,7 +53,7 @@ block content .row div(sixpack-switch="subscription-form") .col-md-12(sixpack-default) - form(ng-show="planName") + form(ng-show="planName",novalidate) .row .col-md-12 .form-group @@ -175,7 +175,7 @@ block content strong {{price.currency.symbol}}{{price.next.total}} .col-md-12(sixpack-when="simple") - form(ng-if="planName") + form(ng-if="planName",novalidate) div.payment-method-toggle a.payment-method-toggle-switch( href @@ -198,16 +198,16 @@ block content strong {{genericError}} div(ng-if="paymentMethod === 'credit_card'") - //- ol - //- li validation.correctCardNumber : {{ validation.correctCardNumber }} - //- li validation.correctExpiry : {{ validation.correctExpiry }} - //- li validation.correctCvv : {{ validation.correctCvv }} - //- li data.month : {{ data.month }} - //- li data.year : {{ data.year }} - //- li data.mmYY : {{ data.mmYY }} - .form-group - label(for="card-name") #{translate("name_on_card")} - input#card-name.form-control(type="text") + .row + .col-xs-6 + .form-group(ng-class="validation.errorFields.first_name ? 'has-error' : ''") + label(for="first-name") #{translate('first_name')} + input#first-name.form-control(type='text', value='', maxlength='255', , onkeyup='', data-recurly="first_name", ng-model="data.first_name", required) + .col-xs-6 + .form-group(for="last-name",ng-class="validation.errorFields.last_name ? 'has-error' : ''") + label(for="last-name") #{translate('last_name')} + input#last-name.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="last_name", ng-model="data.last_name", required) + .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number ? 'has-error' : ''") label(for="card-no") #{translate("credit_card_number")} input#card-no.form-control( diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index ae81d73bb0..859d4710ef 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -101,7 +101,6 @@ define [ $scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv) $scope.updateCountry = -> - console.log 'update country' pricing.address({country:$scope.data.country}).done() $scope.changePaymentMethod = (paymentMethod)-> From b3db66e12b47428da83fe5d8cff83dd883dd4d8c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 19 Aug 2016 17:10:07 +0100 Subject: [PATCH 17/27] Place tracking event on controller load. --- services/web/public/coffee/main/new-subscription.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 859d4710ef..96575eb25a 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -12,10 +12,10 @@ define [ $scope.switchToStudent = ()-> window.location = "/user/subscription/new?planCode=student_free_trial_7_days¤cy=#{$scope.currencyCode}&cc=#{$scope.data.coupon}" + event_tracking.sendMB "subscription-form", { plan : window.plan_code } $scope.paymentMethod = "credit_card" - $scope.data = number: "" month: "" @@ -59,8 +59,6 @@ define [ .done() pricing.on "change", => - event_tracking.sendMB "subscription-form", { plan : pricing.items.plan.code } - $scope.planName = pricing.items.plan.name $scope.price = pricing.price $scope.trialLength = pricing.items.plan.trial?.length From 23ed1e2aa33462c42df5a97b5650611d9518e27a Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 22 Aug 2016 17:03:29 +0100 Subject: [PATCH 18/27] Add form validation. --- services/web/app/views/subscriptions/new.jade | 74 ++++++++++++++----- .../coffee/main/new-subscription.coffee | 35 ++++++--- .../public/stylesheets/app/subscription.less | 9 +++ 3 files changed, 87 insertions(+), 31 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 852a2f5ab3..d26a760e69 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -175,11 +175,15 @@ block content strong {{price.currency.symbol}}{{price.next.total}} .col-md-12(sixpack-when="simple") - form(ng-if="planName",novalidate) + form( + ng-if="planName" + name="simpleCCForm" + novalidate + ) div.payment-method-toggle a.payment-method-toggle-switch( href - ng-click="paymentMethod = 'credit_card'" + ng-click="setPaymentMethod('credit_card');" ng-class="paymentMethod === 'credit_card' ? 'payment-method-toggle-switch-selected' : ''" ) i.fa.fa-cc-mastercard.fa-2x @@ -189,7 +193,7 @@ block content i.fa.fa-cc-amex.fa-2x a.payment-method-toggle-switch( href - ng-click="paymentMethod = 'paypal'" + ng-click="setPaymentMethod('paypal');" ng-class="paymentMethod === 'paypal' ? 'payment-method-toggle-switch-selected' : ''" ) i.fa.fa-cc-paypal.fa-2x @@ -200,40 +204,68 @@ block content div(ng-if="paymentMethod === 'credit_card'") .row .col-xs-6 - .form-group(ng-class="validation.errorFields.first_name ? 'has-error' : ''") + .form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''") label(for="first-name") #{translate('first_name')} - input#first-name.form-control(type='text', value='', maxlength='255', , onkeyup='', data-recurly="first_name", ng-model="data.first_name", required) + input#first-name.form-control( + type="text" + maxlength='255' + data-recurly="first_name" + name="firstName" + ng-model="data.first_name" + required + ) + span.input-feedback-message {{ simpleCCForm.firstName.$error.required ? 'This field is required' : '' }} .col-xs-6 - .form-group(for="last-name",ng-class="validation.errorFields.last_name ? 'has-error' : ''") + .form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-error' : ''") label(for="last-name") #{translate('last_name')} - input#last-name.form-control(type='text', value='', maxlength='255', onkeyup='', data-recurly="last_name", ng-model="data.last_name", required) + input#last-name.form-control( + type="text" + maxlength='255' + data-recurly="last_name" + name="lastName" + ng-model="data.last_name" + required + ) + span.input-feedback-message {{ simpleCCForm.lastName.$error.required ? 'This field is required' : '' }} - .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number ? 'has-error' : ''") + .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number || inputHasError(simpleCCForm.ccNumber) ? 'has-error' : ''") label(for="card-no") #{translate("credit_card_number")} input#card-no.form-control( type="text" ng-model="data.number" - ng-blur="validateCardNumber()" - cc-format-card-number) + name="ccNumber" + required + ng-change="validateCardNumber()" + cc-format-card-number + ) + span.input-feedback-message {{ simpleCCForm.ccNumber.$error.required ? 'This field is required' : 'Please re-check the card number' }} + .row .col-xs-6 - .form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry ? 'has-error' : ''") + .form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry || inputHasError(simpleCCForm.expiry) ? 'has-error' : ''") label #{translate("expiry")} input.form-control( type="text" ng-model="data.mmYY" + name="expiry" + required placeholder="MM / YY" - ng-blur="validateExpiry()" + ng-change="updateExpiry(); validateExpiry()" cc-format-expiry ) + span.input-feedback-message {{ simpleCCForm.expiry.$error.required ? 'This field is required' : 'Please re-check the expiry date' }} + .col-xs-6 - .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv ? 'has-error' : ''") + .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv || inputHasError(simpleCCForm.cvv) ? 'has-error' : ''") label #{translate("security_code")} input.form-control( type="text" ng-model="data.cvv" - ng-blur="validateCvv()" - cc-format-sec-code) + ng-change="validateCvv()" + name="cvv" + required + cc-format-sec-code + ) .form-control-feedback a.form-helper( href @@ -242,22 +274,26 @@ block content tooltip-trigger="mouseenter" tooltip-append-to-body="true" ) ? - .form-group(ng-class="validation.errorFields.country ? 'has-error' : ''") + span.input-feedback-message {{ simpleCCForm.cvv.$error.required ? 'This field is required' : 'Please re-check the security code' }} + + .form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''") label(for="country") #{translate('country')} select#country.form-control( data-recurly="country" ng-model="data.country" + name="country" ng-change="updateCountry()" required ) mixin countries_options() + span.input-feedback-message {{ simpleCCForm.country.$error.required ? 'This field is required' : '' }} div if (showVatField) .form-group label(for="vat-no") #{translate('vat_number')} input#vat-no.form-control( - type='text' + type="text" ng-blur="applyVatNumber()" ng-model="data.vat_number" ) @@ -265,7 +301,7 @@ block content .form-group label(for="coupon-code") #{translate('coupon_code')} input#coupon-code.form-control( - type='text' + type="text" ng-blur="applyCoupon()" ng-model="data.coupon" ) @@ -283,7 +319,7 @@ block content div.payment-submit button.btn.btn-success.btn-block( ng-click="submit()" - ng-disabled="processing" + ng-disabled="processing || !isFormValid(simpleCCForm);" sixpack-convert="payment-left-menu-bottom" ) {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 96575eb25a..f1c81458ef 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -32,12 +32,6 @@ define [ coupon: window.couponCode mmYY: "" - $scope.$watch 'data.mmYY', (newVal) -> - parsedDateObj = ccUtils.parseExpiry newVal - if parsedDateObj? - $scope.data.month = parsedDateObj.month - $scope.data.year = parsedDateObj.year - $scope.validation = correctCardNumber : true correctExpiry: true @@ -81,11 +75,16 @@ define [ $scope.applyVatNumber = -> pricing.tax({tax_code: 'digital', vat_number: $scope.data.vat_number}).done() - $scope.changeCurrency = (newCurrency)-> $scope.currencyCode = newCurrency pricing.currency(newCurrency).done() + $scope.updateExpiry = () -> + parsedDateObj = ccUtils.parseExpiry $scope.data.mmYY + if parsedDateObj? + $scope.data.month = parsedDateObj.month + $scope.data.year = parsedDateObj.year + $scope.validateCardNumber = validateCardNumber = -> if $scope.data.number?.length != 0 $scope.validation.correctCardNumber = recurly.validate.cardNumber($scope.data.number) @@ -98,14 +97,26 @@ define [ if $scope.data.cvv?.length != 0 $scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv) + $scope.inputHasError = inputHasError = (formItem) -> + if !formItem? + return false + + return (formItem.$touched && formItem.$invalid) + + $scope.isFormValid = isFormValid = (form) -> + return $scope.paymentMethod == 'paypal' or + (form.$valid and + $scope.validation.correctCardNumber and + $scope.validation.correctExpiry and + $scope.validation.correctCvv) + $scope.updateCountry = -> pricing.address({country:$scope.data.country}).done() - $scope.changePaymentMethod = (paymentMethod)-> - if paymentMethod == "paypal" - $scope.usePaypal = true - else - $scope.usePaypal = false + $scope.setPaymentMethod = setPaymentMethod = (method) -> + $scope.paymentMethod = method; + $scope.validation.errorFields = {} + $scope.genericError = "" completeSubscription = (err, recurly_token_id) -> $scope.validation.errorFields = {} diff --git a/services/web/public/stylesheets/app/subscription.less b/services/web/public/stylesheets/app/subscription.less index 5e7656adc7..b99fe5d774 100644 --- a/services/web/public/stylesheets/app/subscription.less +++ b/services/web/public/stylesheets/app/subscription.less @@ -21,6 +21,15 @@ margin-bottom: -10px; } +.input-feedback-message { + display: none; + font-size: 0.8em; + + .has-error & { + display: inline-block; + } +} + .payment-submit { padding-top: (@line-height-computed / 2); } From 362d75ef81c49518c355d2e7d51337edabaac83e Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 23 Aug 2016 09:59:46 +0100 Subject: [PATCH 19/27] Log subscription errors. --- services/web/public/coffee/main/new-subscription.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index f1c81458ef..7d9e4f8501 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -121,6 +121,7 @@ define [ completeSubscription = (err, recurly_token_id) -> $scope.validation.errorFields = {} if err? + event_tracking.sendMB "subscription-error", err # We may or may not be in a digest loop here depending on # whether recurly could do validation locally, so do it async $scope.$evalAsync () -> From 1ac31f318e7eebd7cd005412ed68eef477dc8a8d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 23 Aug 2016 10:06:00 +0100 Subject: [PATCH 20/27] Restart validation on form changes. --- services/web/public/coffee/main/new-subscription.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 7d9e4f8501..0cd2dbf372 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -86,14 +86,17 @@ define [ $scope.data.year = parsedDateObj.year $scope.validateCardNumber = validateCardNumber = -> + $scope.validation.errorFields = {} if $scope.data.number?.length != 0 $scope.validation.correctCardNumber = recurly.validate.cardNumber($scope.data.number) $scope.validateExpiry = validateExpiry = -> + $scope.validation.errorFields = {} if $scope.data.month?.length != 0 and $scope.data.year?.length != 0 $scope.validation.correctExpiry = recurly.validate.expiry($scope.data.month, $scope.data.year) $scope.validateCvv = validateCvv = -> + $scope.validation.errorFields = {} if $scope.data.cvv?.length != 0 $scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv) From f79bee789312fdccde0ccc180ad4d9b77d734f4c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 23 Aug 2016 11:27:09 +0100 Subject: [PATCH 21/27] Loading indicator when submitting. --- services/web/app/views/subscriptions/new.jade | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index d26a760e69..f08c2f669a 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -321,7 +321,11 @@ block content ng-click="submit()" ng-disabled="processing || !isFormValid(simpleCCForm);" sixpack-convert="payment-left-menu-bottom" - ) {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} + ) + span(ng-show="processing") + i.fa.fa-refresh.fa-spin + |   + | {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} .col-md-3.col-md-pull-4 From bc16999a4e92e7133a57f7226b443a2154a304db Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 23 Aug 2016 11:55:23 +0100 Subject: [PATCH 22/27] Validate on blur; reset validation on focus. --- services/web/app/views/subscriptions/new.jade | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index f08c2f669a..b32fc05b0b 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -234,8 +234,9 @@ block content type="text" ng-model="data.number" name="ccNumber" + ng-focus="validation.correctCardNumber = true; validation.errorFields.number = false;" + ng-blur="validateCardNumber();" required - ng-change="validateCardNumber()" cc-format-card-number ) span.input-feedback-message {{ simpleCCForm.ccNumber.$error.required ? 'This field is required' : 'Please re-check the card number' }} @@ -248,9 +249,10 @@ block content type="text" ng-model="data.mmYY" name="expiry" - required placeholder="MM / YY" - ng-change="updateExpiry(); validateExpiry()" + ng-focus="validation.correctExpiry = true; validation.errorFields.expiry = false;" + ng-blur="updateExpiry(); validateExpiry()" + required cc-format-expiry ) span.input-feedback-message {{ simpleCCForm.expiry.$error.required ? 'This field is required' : 'Please re-check the expiry date' }} @@ -261,7 +263,8 @@ block content input.form-control( type="text" ng-model="data.cvv" - ng-change="validateCvv()" + ng-focus="validation.correctCvv = true; validation.errorFields.cvv = false;" + ng-blur="validateCvv()" name="cvv" required cc-format-sec-code From 8abf839d55be4381a4d72e06682a6ba54c6e73d7 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 23 Aug 2016 13:36:54 +0100 Subject: [PATCH 23/27] Add country to the paypal form. --- services/web/app/views/subscriptions/new.jade | 8 +++++--- .../web/public/coffee/main/new-subscription.coffee | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index b32fc05b0b..75d806eb7a 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -279,6 +279,8 @@ block content ) ? span.input-feedback-message {{ simpleCCForm.cvv.$error.required ? 'This field is required' : 'Please re-check the security code' }} + + div .form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''") label(for="country") #{translate('country')} select#country.form-control( @@ -290,8 +292,7 @@ block content ) mixin countries_options() span.input-feedback-message {{ simpleCCForm.country.$error.required ? 'This field is required' : '' }} - - div + if (showVatField) .form-group label(for="vat-no") #{translate('vat_number')} @@ -310,6 +311,7 @@ block content ) p(ng-if="paymentMethod === 'paypal'") #{translate("paypal_upgrade")} + div.price-breakdown(ng-if="price.next.tax !== '0.00'") hr.thin span Total: @@ -326,7 +328,7 @@ block content sixpack-convert="payment-left-menu-bottom" ) span(ng-show="processing") - i.fa.fa-refresh.fa-spin + i.fa.fa-spinner.fa-spin |   | {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 0cd2dbf372..f5096c06ea 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -107,13 +107,16 @@ define [ return (formItem.$touched && formItem.$invalid) $scope.isFormValid = isFormValid = (form) -> - return $scope.paymentMethod == 'paypal' or - (form.$valid and - $scope.validation.correctCardNumber and - $scope.validation.correctExpiry and - $scope.validation.correctCvv) + if $scope.paymentMethod == 'paypal' + return $scope.data.country != "" + else + return (form.$valid and + $scope.validation.correctCardNumber and + $scope.validation.correctExpiry and + $scope.validation.correctCvv) $scope.updateCountry = -> + console.log $scope.data.country pricing.address({country:$scope.data.country}).done() $scope.setPaymentMethod = setPaymentMethod = (method) -> From 6a72c2fce08d2e9ef0602d7c08bc7c7c59556b99 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 23 Aug 2016 16:46:23 +0100 Subject: [PATCH 24/27] comment settings back out --- services/web/config/settings.defaults.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 56f3ee2b81..10fef74a11 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -114,9 +114,9 @@ module.exports = settings = showSocialButtons: false showComments: false - cdn: - web: - host:"http://nowhere.sharelatex.dev" + # cdn: + # web: + # host:"http://nowhere.sharelatex.dev" # darkHost:"http://cdn.sharelatex.dev:3000" # Where your instance of ShareLaTeX can be found publically. Used in emails From fcc75854449c5e690ae097a44d9efdbe98dc2595 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 23 Aug 2016 16:58:39 +0100 Subject: [PATCH 25/27] put jquery as first script in head tag --- services/web/app/views/layout.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index f1bfeedfee..88fe4187fe 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -52,7 +52,6 @@ html(itemscope, itemtype='http://schema.org/Product') window.csrfToken = "#{csrfToken}"; block scripts - script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false})) script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false})) script(type="text/javascript"). var noCdnKey = "nocdn=true" @@ -61,6 +60,7 @@ html(itemscope, itemtype='http://schema.org/Product') if (cdnBlocked && !noCdnAlreadyInUrl) { window.location.search += '&'+noCdnKey; } + script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false})) script. window.sharelatex = { From 934e908697b95969f3c4581992604144429e9f9e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 23 Aug 2016 17:00:13 +0100 Subject: [PATCH 26/27] just use plain req.ip for logging --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 60f1b682e5..d3b9d9a24b 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -64,8 +64,7 @@ module.exports = (app, webRouter, apiRouter)-> cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked if cdnBlocked and !req.session.cdnBlocked? - ip = req.ip || req.socket?.socket?.remoteAddress || req.socket?.remoteAddress - logger.log user_id:req?.session?.user?._id, ip:ip, "cdnBlocked for user, not using it" + logger.log user_id:req?.session?.user?._id, ip:req?.ip, "cdnBlocked for user, not using it and turning it off for future requets" req.session.cdnBlocked = true isDark = req.headers?.host?.slice(0,4)?.toLowerCase() == "dark" From c82ab65077806717760679c2297355dba3fa9be7 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Wed, 24 Aug 2016 11:02:53 +0100 Subject: [PATCH 27/27] Bind to an object to avoid writing in different . --- services/web/app/views/subscriptions/new.jade | 16 ++++++++-------- .../public/coffee/main/new-subscription.coffee | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 75d806eb7a..31c8205e7c 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -60,19 +60,19 @@ block content .row .col-md-6 label.radio-inline - input.paymentTypeOption(type="radio",value="credit_card", ng-model="paymentMethod") + input.paymentTypeOption(type="radio",value="credit_card", ng-model="paymentMethod.value") i.fa.fa-cc-mastercard.fa-3x span   i.fa.fa-cc-visa.fa-3x .col-md-6 label.radio-inline - input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod") + input.paymentTypeOption(type="radio", value="paypal", ng-model="paymentMethod.value") i.fa.fa-cc-paypal.fa-3x .alert.alert-warning.small(ng-show="genericError") strong {{genericError}} - span(ng-hide="paymentMethod == 'paypal'") + span(ng-hide="paymentMethod.value == 'paypal'") .row .col-md-12 .form-group @@ -184,7 +184,7 @@ block content a.payment-method-toggle-switch( href ng-click="setPaymentMethod('credit_card');" - ng-class="paymentMethod === 'credit_card' ? 'payment-method-toggle-switch-selected' : ''" + ng-class="paymentMethod.value === 'credit_card' ? 'payment-method-toggle-switch-selected' : ''" ) i.fa.fa-cc-mastercard.fa-2x span   @@ -194,14 +194,14 @@ block content a.payment-method-toggle-switch( href ng-click="setPaymentMethod('paypal');" - ng-class="paymentMethod === 'paypal' ? 'payment-method-toggle-switch-selected' : ''" + ng-class="paymentMethod.value === 'paypal' ? 'payment-method-toggle-switch-selected' : ''" ) i.fa.fa-cc-paypal.fa-2x .alert.alert-warning.small(ng-show="genericError") strong {{genericError}} - div(ng-if="paymentMethod === 'credit_card'") + div(ng-if="paymentMethod.value === 'credit_card'") .row .col-xs-6 .form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''") @@ -310,7 +310,7 @@ block content ng-model="data.coupon" ) - p(ng-if="paymentMethod === 'paypal'") #{translate("paypal_upgrade")} + p(ng-if="paymentMethod.value === 'paypal'") #{translate("paypal_upgrade")} div.price-breakdown(ng-if="price.next.tax !== '0.00'") hr.thin @@ -330,7 +330,7 @@ block content span(ng-show="processing") i.fa.fa-spinner.fa-spin |   - | {{ paymentMethod === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} + | {{ paymentMethod.value === 'credit_card' ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_paypal_btn")}' }} .col-md-3.col-md-pull-4 diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index f5096c06ea..5c9d5cdd86 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -14,7 +14,8 @@ define [ event_tracking.sendMB "subscription-form", { plan : window.plan_code } - $scope.paymentMethod = "credit_card" + $scope.paymentMethod = + value: "credit_card" $scope.data = number: "" @@ -107,7 +108,7 @@ define [ return (formItem.$touched && formItem.$invalid) $scope.isFormValid = isFormValid = (form) -> - if $scope.paymentMethod == 'paypal' + if $scope.paymentMethod.value == 'paypal' return $scope.data.country != "" else return (form.$valid and @@ -116,11 +117,10 @@ define [ $scope.validation.correctCvv) $scope.updateCountry = -> - console.log $scope.data.country pricing.address({country:$scope.data.country}).done() $scope.setPaymentMethod = setPaymentMethod = (method) -> - $scope.paymentMethod = method; + $scope.paymentMethod.value = method; $scope.validation.errorFields = {} $scope.genericError = "" @@ -142,7 +142,7 @@ define [ currencyCode:pricing.items.currency plan_code:pricing.items.plan.code coupon_code:pricing.items?.coupon?.code || "" - isPaypal: $scope.paymentMethod == 'paypal' + isPaypal: $scope.paymentMethod.value == 'paypal' address: address1: $scope.data.address1 address2: $scope.data.address2 @@ -170,7 +170,7 @@ define [ $scope.submit = -> $scope.processing = true - if $scope.paymentMethod == 'paypal' + if $scope.paymentMethod.value == 'paypal' opts = { description: $scope.planName } recurly.paypal opts, completeSubscription else