Merge branch 'master' into dcl-olsl760b

This commit is contained in:
Douglas Lovell 2018-08-31 14:04:00 -03:00
commit 404e768d24
22 changed files with 232 additions and 101 deletions

View file

@ -46,11 +46,15 @@ module.exports =
else if err? and err?.message?.indexOf("could not be validated") != -1
logger.log {oldEmail, newEmail},
"unable to change email in newsletter, user has previously unsubscribed or new email already exist on list"
return callback(err)
return callback()
else if err? and err.message.indexOf("is already a list member") != -1
logger.log {oldEmail, newEmail},
"unable to change email in newsletter, new email is already on mailing list"
return callback()
else if err? and err?.message?.indexOf("looks fake or invalid") != -1
logger.log {oldEmail, newEmail},
"unable to change email in newsletter, email looks fake to mailchimp"
return callback()
else if err?
logger.err {err, oldEmail, newEmail}, "error changing email in newsletter"
return callback(err)

View file

@ -18,9 +18,9 @@ module.exports =
jobs =
partOfGroup: (cb)->
SubscriptionGroupHandler.isUserPartOfGroup user.id, licence.group_subscription_id, cb
SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.group_subscription_id, cb
subscription: (cb)->
SubscriptionLocator.getUsersSubscription user.id, cb
SubscriptionLocator.getUsersSubscription user._id, cb
async.series jobs, (err, results)->
{ partOfGroup, subscription } = results

View file

@ -15,30 +15,33 @@ planFeatures = require './planFeatures'
module.exports = SubscriptionController =
plansPage: (req, res, next) ->
plans = SubscriptionViewModelBuilder.buildViewModel()
viewName = "subscriptions/plans"
if req.query.v?
viewName = "#{viewName}_#{req.query.v}"
logger.log viewName:viewName, "showing plans page"
currentUser = null
if Settings.overleaf? && !req.query.plns
res.redirect "#{Settings.overleaf.host}/plans"
else
plans = SubscriptionViewModelBuilder.buildViewModel()
viewName = "subscriptions/plans"
if req.query.v?
viewName = "#{viewName}_#{req.query.v}"
logger.log viewName:viewName, "showing plans page"
currentUser = null
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
return next(err) if err?
render = () ->
res.render viewName,
title: "plans_and_pricing"
plans: plans
gaExperiments: Settings.gaExperiments.plansPage
recomendedCurrency:recomendedCurrency
planFeatures: planFeatures
user_id = AuthenticationController.getLoggedInUserId(req)
if user_id?
UserGetter.getUser user_id, {signUpDate: 1}, (err, user) ->
return next(err) if err?
currentUser = user
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
return next(err) if err?
render = () ->
res.render viewName,
title: "plans_and_pricing"
plans: plans
gaExperiments: Settings.gaExperiments.plansPage
recomendedCurrency:recomendedCurrency
planFeatures: planFeatures
user_id = AuthenticationController.getLoggedInUserId(req)
if user_id?
UserGetter.getUser user_id, {signUpDate: 1}, (err, user) ->
return next(err) if err?
currentUser = user
render()
else
render()
else
render()
#get to show the recurly.js page
paymentPage: (req, res, next) ->

View file

@ -9,11 +9,7 @@ module.exports =
apply: (webRouter, privateApiRouter, publicApiRouter) ->
return unless Settings.enableSubscriptions
if Settings.overleaf?
webRouter.get '/user/subscription/plans', (req, res) ->
res.redirect "#{Settings.overleaf.host}/plans"
else
webRouter.get '/user/subscription/plans', SubscriptionController.plansPage
webRouter.get '/user/subscription/plans', SubscriptionController.plansPage
webRouter.get '/user/subscription', AuthenticationController.requireLogin(), SubscriptionController.userSubscriptionPage

View file

@ -112,7 +112,8 @@ module.exports = UserUpdater =
else if res.n == 0 # TODO: Check n or nMatched?
return callback(new Error('Default email does not belong to user'))
else
NewsletterManager.changeEmail oldEmail, email, callback
NewsletterManager.changeEmail oldEmail, email, ->
callback()

View file

@ -6,7 +6,7 @@
.row
.col-md-6
h3 #{translate("faq_how_free_trial_works_question")}
p #{translate('faq_how_free_trial_works_answer', { len:'{{trial_len}}' })}
p #{translate('faq_how_does_free_trial_works_answer', { appName:'{{settings.appName}}', len:'{{trial_len}}' })}
.col-md-6
h3 #{translate('faq_change_plans_question')}
p #{translate('faq_change_plans_answer')}
@ -16,11 +16,18 @@
p #{translate('faq_do_collab_need_premium_answer')}
.col-md-6
h3 #{translate('faq_need_more_collab_question')}
p !{translate('faq_need_more_collab_answer', { referFriendsLink: '<a href="/user/bonus">' + translate('referring_your_friends') + '</a>'})}
if settings.overleaf
p !{translate('faq_need_more_collab_answer', { referFriendsLink: translate('referring_your_friends') })}
else
p !{translate('faq_need_more_collab_answer', { referFriendsLink: '<a href="/user/bonus">' + translate('referring_your_friends') + '</a>'})}
.row
.col-md-6
h3 #{translate('faq_purchase_more_licenses_question')}
p !{translate('faq_purchase_more_licenses_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
if settings.overleaf
p !{translate('faq_purchase_more_licenses_answer', { groupLink: translate('discounted_group_accounts') })}&nbsp;
a(href, ng-click="openGroupPlanModal()") #{translate("get_in_touch_for_details")}
else
p !{translate('faq_purchase_more_licenses_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
.col-md-6
h3 #{translate('faq_monthly_or_annual_question')}
p #{translate('faq_monthly_or_annual_answer')}
@ -30,4 +37,7 @@
p #{translate('faq_how_to_pay_answer')}
.col-md-6
h3 #{translate('faq_pay_by_invoice_question')}
p !{translate('faq_pay_by_invoice_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
if settings.overleaf
p !{translate('faq_pay_by_invoice_answer', { groupLink: translate('discounted_group_accounts') })}
else
p !{translate('faq_pay_by_invoice_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}

View file

@ -1,20 +1,23 @@
//- Buy Buttons
mixin btn_buy_collaborator(location)
a.btn.btn-info(
a.btn(
class=settings.overleaf ? 'btn-primary' : 'btn-info'
ng-href="/user/subscription/new?planCode={{ getCollaboratorPlanCode() }}&currency={{currencyCode}}",
ng-click="signUpNowClicked('collaborator','" + location + "')"
)
span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
mixin btn_buy_free(location)
a.btn.btn-info(
a.btn(
class=settings.overleaf ? 'btn-primary' : 'btn-info'
href="/register"
style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
ng-click="signUpNowClicked('free','" + location + "')"
)
span.text-capitalize #{translate('get_started_now')}
mixin btn_buy_professional(location)
a.btn.btn-info(
a.btn(
class=settings.overleaf ? 'btn-primary' : 'btn-info'
ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}&currency={{currencyCode}}"
ng-click="signUpNowClicked('professional','" + location + "')"
)
@ -22,13 +25,15 @@ mixin btn_buy_professional(location)
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
mixin btn_buy_student(location, plan)
if plan == 'annual'
a.btn.btn-info(
a.btn(
class=settings.overleaf ? 'btn-primary' : 'btn-info'
ng-href="/user/subscription/new?planCode=student-annual&currency={{currencyCode}}",
ng-click="signUpNowClicked('student-annual','" + location + "')"
) #{translate("buy_now")}
else
//- planQueryString will contain _free_trial_7_days
a.btn.btn-info(
a.btn(
class=settings.overleaf ? 'btn-primary' : 'btn-info'
ng-href="/user/subscription/new?planCode=student{{planQueryString}}&currency={{currencyCode}}",
ng-click="signUpNowClicked('student-monthly','" + location + "')"
) #{translate("start_free_trial")}

View file

@ -0,0 +1,25 @@
.row.row-spaced-large
.col-md-12
.page-header.plans-header.plans-subheader.text-centered
h2 #{translate('in_good_company')}
.row
.col-md-6
div
.row
.col-md-3
.circle-img
img(src=buildImgPath('advocates/erdogmus.jpg') alt="Professor Erdogmus")
.col-md-9
blockquote
p The ability to track changes and the real-time collaborative nature is what sets ShareLaTeX apart.
footer Professor Erdogmus, Northeastern University
.col-md-6
div
.row
.col-md-3
.circle-img
img(src=buildImgPath('advocates/henderson.jpg') alt="Rob Henderson")
.col-md-9
blockquote
p ShareLaTeX has proven to be a powerful and robust collaboration tool that is widely used in our School.
footer Rob Henderson, School Of Informatics And Computing - Indiana University

View file

@ -14,7 +14,7 @@ block content
.container(ng-controller="NewSubscriptionController" ng-cloak)
.row.card-group
.col-md-5.col-md-push-4
.card.card-highlighted
.card.card-highlighted(class=settings.overleaf ? 'card-border' : '')
.page-header
.row
.col-xs-9
@ -250,12 +250,13 @@ block content
hr
p.small.text-center We're confident that you'll love #{settings.appName}, 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 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
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;")
if !settings.overleaf
hr
span &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
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").

View file

@ -15,7 +15,7 @@ block content
.row
.col-md-12
.page-header.centered.plans-header.text-centered
h1.text-capitalize #{translate('instant_access')}
h1.text-capitalize #{translate('get_instant_access_to')} #{settings.appName}
.row
.col-md-8.col-md-offset-2
p.text-centered #{translate("sl_benefits_plans")}
@ -89,7 +89,13 @@ block content
span #{translate('reduce_costs_group_licenses')}
br
br
a.btn.btn-info(href="/i/university/groups") #{translate('find_out_more')}
if settings.overleaf
a.btn.btn-default(
href
ng-click="openGroupPlanModal()"
) #{translate('find_out_more')}
else
a.btn.btn-info(href="/i/university/groups") #{translate('find_out_more')}
div
.row.row-spaced-large
@ -107,31 +113,8 @@ block content
.col-sm-12(ng-if="ui.view == 'student'")
+table_student
.row.row-spaced-large
.col-md-12
.page-header.plans-header.plans-subheader.text-centered
h2 #{translate('in_good_company')}
.row
.col-md-6
div
.row
.col-md-3
.circle-img
img(src=buildImgPath('advocates/erdogmus.jpg') alt="Professor Erdogmus")
.col-md-9
blockquote
p The ability to track changes and the real-time collaborative nature is what sets ShareLaTeX apart.
footer Professor Erdogmus, Northeastern University
.col-md-6
div
.row
.col-md-3
.circle-img
img(src=buildImgPath('advocates/henderson.jpg') alt="Rob Henderson")
.col-md-9
blockquote
p ShareLaTeX has proven to be a powerful and robust collaboration tool that is widely used in our School.
footer Rob Henderson, School Of Informatics And Computing - Indiana University
if !settings.overleaf
include _plans_quotes
include _plans_faq
@ -139,8 +122,13 @@ block content
.col-md-12
.plans-header.plans-subheader.text-centered
h2 #{translate('still_have_questions')}
button.btn.btn-info.btn-header.text-capitalize(ng-controller="ContactGeneralModal" ng-click="openModal()") #{translate('get_in_touch')}
button.btn.btn-header.text-capitalize(
class=settings.overleaf ? 'btn-default' : 'btn-info'
ng-controller="ContactGeneralModal"
ng-click="openModal()"
) #{translate('get_in_touch')}
!= moduleIncludes("contactModalGeneral", locals)
.row.row-spaced
include _modal_group_inquiry

View file

@ -202,6 +202,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
) {{ $select.selected.name }}
ui-select-choices(
repeat="university in $ctrl.universities | filter: $select.search"
refresh="$ctrl.handleFreeformInputChange($select, 'name');"
refresh-delay="10"
)
span(
ng-bind="university.name"
@ -219,6 +221,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
) {{ $select.selected }}
ui-select-choices(
repeat="role in $ctrl.roles | filter: $select.search"
refresh="$ctrl.handleFreeformInputChange($select);"
refresh-delay="10"
)
span(
ng-bind="role"
@ -237,6 +241,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
) {{ $select.selected }}
ui-select-choices(
repeat="department in $ctrl.departments | filter: $select.search"
refresh="$ctrl.handleFreeformInputChange($select);"
refresh-delay="10"
)
span(
ng-bind="department"

View file

@ -11,6 +11,18 @@ define [
ctrl.addUniversityToSelection = (universityName) ->
{ name: universityName, isUserSuggested: true }
ctrl.handleFreeformInputChange = ($select, propertyToMatch) ->
if !$select.search? or $select.search == ""
return
resultingItem = $select.search
if $select.tagging?.fct?
resultingItem = $select.tagging.fct $select.search
if propertyToMatch?
matchingItem = _.find $select.items, (item) -> item[propertyToMatch] == $select.search
if matchingItem?
resultingItem = matchingItem
$select.searchInput.scope().$broadcast "uis:select", resultingItem
# Populates the countries dropdown
UserAffiliationsDataService
.getCountries()

View file

@ -0,0 +1,14 @@
.plans {
blockquote {
footer{
/* accessibility fix */
color: @ol-blue-gray-3;
}
}
.plans-header{
h1, h2 {
color: @gray-dark;
}
}
}

View file

@ -4,8 +4,9 @@
margin-bottom: @line-height-computed;
}
.best-value {
color: @red;
color: @brand-secondary;
line-height: @line-height-computed;
text-transform: capitalize;
}
blockquote {
footer{
@ -20,6 +21,22 @@
text-shadow: 0 0 0;
}
.card .btn { white-space:normal; }
.card.features {
margin-top: @line-height-computed;
i {
color: @red;
}
p {
margin: 0;
}
}
.btn-header {
font-family: @font-family-sans-serif;
margin-left: 10px;
margin-top: -10px;
text-shadow: 0 0 0;
}
.card .btn { white-space:normal; }
.card-group {
.card-highlighted {
padding-top: @line-height-computed * 2;
@ -27,10 +44,10 @@
}
}
.card-first, .card-last {
background: @white-med;
background: @plans-non-highlighted;
}
.card-highlighted {
border: @highlight-border solid @gray-lighter;
border: @border-width-base solid @border-color-base;
padding-top: 10px!important;
.best-value {
margin-bottom: 15px;
@ -51,7 +68,7 @@
width: 120px;
height: 120px;
border-radius: 50%;
background-color: @red;
background-color: @brand-secondary;
color: white;
white-space: nowrap;
span.small {
@ -148,10 +165,8 @@
Plans Test
*/
@best-val-height: 35px;
@highlight-border: 3px;
@highlight-color: #d3584b;
@border-width-base: 3px;
@gray-med: #6d6d6d;
@white-med: #fdfdfd;
/* Media Queries */
@media (max-width: @screen-sm-max) {
@ -192,7 +207,7 @@
*/
.plans-table {
border: 1px solid @gray-lighter;
background-color: @white-med;
background-color: @plans-non-highlighted;
margin: @best-val-height 0 15px 0;
table-layout: fixed;
width: 100%;
@ -215,8 +230,8 @@
border-top: 0;
font-family: @headings-font-family;
font-size: @font-size-h2;
font-weight: @headings-font-weight;
line-height: @headings-line-height;
font-weight: @headings-font-weight;
line-height: @headings-line-height;
padding: 18px;
}
@ -246,13 +261,13 @@
border-bottom: 0;
padding: 18px;
}
/* highlighted column */
/* highlighted column */
td:nth-child(3) {
position: relative;
/* keep here position here, otherwise messes up border on safari when there is a bg color */
&:before {
/* needed for safafi */
border-top: 1px solid @gray-lighter;
border-top: 1px solid @border-color-base;
content: '';
left: 0;
position: absolute;
@ -277,17 +292,17 @@
/* highlighted column */
td:nth-child(3), th:nth-child(3) {
background-color: white;
border-left: @highlight-border solid @gray-lighter;
border-right: @highlight-border solid @gray-lighter;
border-left: @border-width-base solid @border-color-base;
border-right: @border-width-base solid @border-color-base;
}
.outer {
left: -@highlight-border;
right: -@highlight-border;
left: -@border-width-base;
right: -@border-width-base;
position: absolute;
.outer-content {
background: white;
border: @highlight-border solid @gray-lighter;
border: @border-width-base solid @border-color-base;
border-radius: @border-radius-base;
font-size: @font-size-base;
font-family: @font-family-sans-serif;
@ -317,7 +332,7 @@
/* highlight rows on hover */
tr:hover {
td {
background-color: @gray-lightest;
background-color: @table-hover-bg;
}
}
tr:first-child:hover {
@ -332,7 +347,7 @@
/* tooltip */
sup {
color: @red;
color: @brand-secondary;
cursor: pointer;
margin-left: 5px;
}

View file

@ -44,16 +44,16 @@
border: solid 1px @gray-lighter;
border-radius: @border-radius-large 0 0 @border-radius-large;
padding: (@line-height-computed / 2);
color: @gray;
color: @btn-switch-color;
&:hover,
&:focus {
color: @gray;
color: @btn-switch-color;
text-decoration: none;
}
&:hover {
color: @gray-dark;
color: @btn-switch-hover-color;
}
& + & {
@ -62,12 +62,12 @@
}
&-selected {
color: @link-color;
color: @link-active-color;
box-shadow: inset 0 -2px 0 0;
&:hover,
&:focus {
color: @link-color;
color: @link-active-color;
}
}
}

View file

@ -50,4 +50,8 @@
border-bottom-right-radius: @border-radius-base;
}
}
}
.card-border {
border: @border-width-base solid @border-color-base;
}

View file

@ -0,0 +1,7 @@
.nav-pills {
> li {
> a {
border-radius: @btn-border-radius-base;
}
}
}

View file

@ -123,8 +123,14 @@
// Links rendered as pills
> a {
border-radius: @nav-pills-border-radius;
border: 2px solid @link-color;
border: 2px solid @nav-pills-link-color;
color: @nav-pills-link-color;
padding: 8px 13px;
&:hover,
&:focus {
background-color: @nav-pills-link-hover-bg;
border: 2px solid @nav-pills-link-hover-bg;
}
}
+ li {
margin-left: 2px;
@ -136,6 +142,7 @@
&:hover,
&:focus {
color: @nav-pills-active-link-hover-color;
border: 2px solid @nav-pills-active-link-hover-bg;
background-color: @nav-pills-active-link-hover-bg;
}
}
@ -247,4 +254,4 @@
margin-top: -1px;
// Remove the top rounded corners here since there is a hard edge above the menu
.border-top-radius(0);
}
}

View file

@ -13,6 +13,7 @@
//** Global textual link color.
@link-color: @brand-primary;
@link-active-color: @link-color;
//** Link hover color set via `darken()` function.
@link-hover-color: darken(@link-color, 15%);
@ -87,6 +88,12 @@
@border-radius-base: 3px;
@border-radius-large: 5px;
@border-radius-small: 2px;
@border-width-base: 3px;
@border-color-base: @gray-lighter;
@btn-switch-color: @gray;
@btn-switch-hover-color: @gray-dark;
//** Global color for active items (e.g., navs or dropdowns).
@component-active-color: #fff;
//** Global background color for active items (e.g., navs or dropdowns).
@ -370,6 +377,9 @@
@nav-tabs-justified-active-link-border-color: @body-bg;
//== Pills
@nav-pills-link-color: @link-color;
@nav-pills-link-hover-color: @nav-link-hover-bg;
@nav-pills-link-hover-bg: @nav-link-hover-bg;
@nav-pills-border-radius: @border-radius-base;
@nav-pills-active-link-hover-bg: @component-active-bg;
@nav-pills-active-link-hover-color: @component-active-color;
@ -412,6 +422,10 @@
@pager-disabled-color: @pagination-disabled-color;
// Plans
@table-hover-bg: @gray-lightest;
@plans-non-highlighted: #fdfdfd;
//== Jumbotron
//
//##

View file

@ -34,14 +34,22 @@
@navbar-default-border : transparent;
@navbar-brand-image-url : url(/img/ol-brand/overleaf-white.svg);
@navbar-default-link-bg : transparent;
@nav-pills-active-link-hover-bg: @ol-dark-green;
@nav-pills-link-color : @btn-default-bg;
@nav-pills-link-hover-bg : darken(@ol-blue-gray-4, 8%); // match button-variant mixin
// Backgrounds
@body-bg : #FFF;
@content-alt-bg-color : @ol-blue-gray-1;
// Border
@border-color-base: @ol-blue-gray-2;
// Typography
@text-small-color : @ol-type-color;
@text-color : @ol-type-color;
@link-color : @ol-blue;
@link-active-color : @ol-dark-green;
@link-hover-color : @ol-dark-blue;
// Button colors and sizing
@ -68,6 +76,9 @@
@btn-info-bg : @ol-blue;
@btn-info-border : transparent;
@btn-switch-color : @ol-blue-gray-4;
@btn-switch-hover-color : darken(@ol-blue-gray-4, 8%);
// Padding
@padding-xs-horizontal : 8px;
@ -308,6 +319,10 @@
@log-line-no-color : #FFF;
@log-hints-color : @ol-blue-gray-4;
// Plans
@table-hover-bg : @ol-blue-gray-0;
@plans-non-highlighted : white;
// Portals
@black-alpha-strong : rgba(0,0,0,0.8);
@ -360,6 +375,7 @@
@purple: #7a43b6;
@brand-primary: @ol-green;
@brand-secondary: @ol-dark-green;
@brand-success: @green;
@brand-info: @ol-blue;
@brand-warning: @orange;

View file

@ -20,6 +20,7 @@
@purple: #7a43b6;
@brand-primary: @red;
@brand-secondary: @red;
@brand-success: @green;
@brand-info: @blue;
@brand-warning: @orange;

View file

@ -8,6 +8,7 @@
@import "_ol_style_includes.less";
@import "components/embed-responsive.less";
@import "components/icons.less";
@import "components/navs-ol.less";
@import "components/pagination.less";
@import "components/publish-modal.less";
@ -15,4 +16,5 @@
@import "app/about.less";
@import "app/blog-posts.less";
@import "app/cms-page.less";
@import "app/plans-ol.less";
@import "app/portals.less";