From b3bfa2dc26a29343dad51081a9842afe331f7301 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 12:27:58 +0100 Subject: [PATCH 1/9] Add plans page --- services/web/app/views/layout.jade | 3 - .../web/app/views/subscriptions/plans.jade | 358 ++++++++++-------- services/web/public/coffee/app/main.coffee | 1 + .../web/public/coffee/app/main/plans.coffee | 6 + .../web/public/stylesheets/app/plans.less | 124 ++++++ services/web/public/stylesheets/style.less | 1 + 6 files changed, 333 insertions(+), 160 deletions(-) create mode 100644 services/web/public/coffee/app/main/plans.coffee create mode 100644 services/web/public/stylesheets/app/plans.less diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 837953a0ef..c0c5847faa 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -3,9 +3,6 @@ html(itemscope, itemtype='http://schema.org/Product') block vars head - - if (typeof(gaExperimentCode) !== "undefined" && gaExperimentCode) - #{gaExperimentCode} - - if (typeof(priority_title) !== "undefined" && priority_title) title= title + ' - Online LaTeX Editor ShareLaTeX' - else diff --git a/services/web/app/views/subscriptions/plans.jade b/services/web/app/views/subscriptions/plans.jade index 892796d776..02520c3485 100644 --- a/services/web/app/views/subscriptions/plans.jade +++ b/services/web/app/views/subscriptions/plans.jade @@ -1,166 +1,210 @@ extends ../layout -mixin liSection(feature) - | #{feature.text} - -if(feature.comingSoon) - span.label.label-info coming soon - -if(feature.beta) - span.label.label-warning beta - - -mixin plan(plan, cssClass, monthly) - .pricing-table - ul(class=cssClass) - - li.pricing-header-row-1 - .package-title - h2.no-bold #{plan.name} - - li.pricing-header-row-2 - .package-price - if plan.price == 0 - h1.free Free forever - else - h1.no-bold - | $#{plan.price/100} - if monthly - span.cents /month - else - span.cents /year - - - var odd = true - -each feature in plan.featureDescription - - odd = !odd - - if(odd) - li.pricing-content-row-odd - mixin liSection(feature) - - else - li.pricing-content-row-even - mixin liSection(feature) - - li.pricing-footer - - var href = '/user/subscription/new?planCode='+plan.planCode - - - planIsPersonal = plan.planCode.indexOf("personal") != -1 - - userNotLoggedIn = session && !session.user - -if(planIsPersonal) - - href = "/register" - -else if(userNotLoggedIn) - - href = "/register?redir="+href - a.btn.btn-success(href='#{href}', ga_PlanType=plan.planCode).sign_up_now - | Sign Up Now! - - block content - .container - .row - .span12.span-box - .page-header - h1 Choose your plan - blockquote.quote.pull-right - p - | This is one of the most useful resources I have ever found on the Internet. - br - | Fantastic execution and thoughtful attention to detail make this product shine! - small Benjamin Shepherd, Waterloo University + .content-alt + .content.plans(ng-controller="PlansController") + .container + .row + .col-md-12 + .page-header.centered.plans-header.text-centered + h1 Start Your 30-Day Free Trial Today .row - .span12 - .offset3 - ul.nav.nav-pills.pricing-pills - li.active - a(href="#", data-target=".monthly-pricing", data-toggle="tab") Monthly - li - a(href="#", data-target=".annual-pricing", data-toggle="tab") Annual - li - a(href="#", data-target=".student-pricing", data-toggle="tab") Half price student plans + .col-md-12 + p.text-centered ShareLaTeX is the world's easiest to use LaTeX editor. You'll stay up to date with your collaborators, keep track of all changes to your work, and use our up to date and fast compiling environment from anywhere in the world. + + .row(ng-cloak) + .col-md-12 + ul.nav.nav-pills + li(ng-class="{'active': ui.view == 'monthly'}") + a( + href, + ng-click="ui.view = 'monthly'" + ) Monthly + li(ng-class="{'active': ui.view == 'annual'}") + a( + href + ng-click="ui.view = 'annual'" + ) Annual + li(ng-class="{'active': ui.view == 'student'}") + a( + href, + ng-click="ui.view = 'student'" + ) Half Price Student Plans + + .row(ng-cloak) + .col-md-12 + .card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'") + .card + .card-header + h2 Personal + .circle Free + ul.list-unstyled + li Only one extra collaborator per
project + li   + li + br + a.btn.btn-primary(href="/register") Sign up now! + .card.highlighted + .card-header + h2 Collaborator + .circle + span(ng-if="ui.view == 'monthly'") + | $15 + span.small / mo + span(ng-if="ui.view == 'annual'") + | $180 + span.small / yr + ul.list-unstyled + li 10 extra collaborators per project + li Full document history + li Sync to Dropbox + li + br + a.btn.btn-primary( + ng-href="/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '_annual' || ''}}" + ) Sign up now! + .card + .card-header + h2 Professional + .circle + span(ng-if="ui.view == 'monthly'") + | $30 + span.small / mo + span(ng-if="ui.view == 'annual'") + | $360 + span.small / yr + ul.list-unstyled + li Unlimited collaborators per project + li Full document history + li Sync to Dropbox + li + br + a.btn.btn-primary( + ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '_annual' || ''}}" + ) Sign up now! + + .card-group.text-centered(ng-if="ui.view == 'student'") + .card + .card-header + h2 Personal + .circle Free + ul.list-unstyled + li Only one extra collaborator per
project + li   + li + br + a.btn.btn-primary(href="/register") Sign up now! + .card.highlighted + .card-header + h2 Student + .circle + span + | $8 + span.small / mo + ul.list-unstyled + li 6 extra collaborators per project + li Full document history + li Sync to Dropbox + li + br + a.btn.btn-primary( + ng-href="/user/subscription/new?planCode=student" + ) Sign up now! + .card + .card-header + h2 Student (Annual) + .circle + span + | $80 + span.small / yr + ul.list-unstyled + li 6 extra collaborators per project + li Full document history + li Sync to Dropbox + li + br + a.btn.btn-primary( + ng-href="/user/subscription/new?planCode=student_annual" + ) Sign up now! + + .row(ng-cloak) + p.text-centered Choose the plan that works for you with our 30-day money-back guarantee. Cancel at any time. .row - .span12 - .page-header - h2 Individual Plans - - .pricing-steelblue.pricing-row - .tab-content - .tab-pane.active.monthly-pricing - .row - .span12.tagline - p An online LaTeX editor for collaborating on the same LaTeX project and editing together in real-time. You’ll never go out of sync with your collaborators again, or lose track of any changes. - .row - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['collaborator'], "big", true) - .span4 - mixin plan(plans.allPlans['professional'], "", true) - - .tab-pane.annual-pricing - .row - .span12.tagline - p Collaborate on the same LaTeX project and edit together in real-time. You’ll never go out of sync with your collaborators again, or lose track of any changes. - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['collaborator-annual'], "big", false) - .span4 - mixin plan(plans.allPlans['professional-annual'], "", false) - - .tab-pane.student-pricing - .row - .span8.offset2.tagline - p Getting started and working with LaTeX has never been so easy. Start creating beautiful work now. - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['student'], "big", true) - .span4 - mixin plan(plans.allPlans['student-annual'], "", false) - - .row - .span12.ab-guarantee-shown(style="text-align: center;") - h2 30 day money back guarantee, cancel anytime. - - .pricing-steelblue.pricing-row - .tab-content - .tab-pane.active.monthly-pricing - .page-header - h2 Group Plans - .row - .span12.tagline - p Improve the workflow of your research group by unlocking ShareLaTeX's premium features for everyone on your team - .row - .span4 - mixin plan(plans.groupMonthlyPlans[0], "", true) - .span4 - mixin plan(plans.groupMonthlyPlans[1], "big", true) - .span4 - mixin plan(plans.groupMonthlyPlans[2], "", true) - .tab-pane.annual-pricing - .page-header - h2 Group Plans - .row - .span12.tagline - p Improve the workflow of your research group by unlocking ShareLaTeX's premium features for everyone on your team - .row - .span4 - mixin plan(plans.groupAnnualPlans[0], "", false) - .span4 - mixin plan(plans.groupAnnualPlans[1], "big", false) - .span4 - mixin plan(plans.groupAnnualPlans[2], "", false) - .tab-pane.student-pricing - - - .row - .span6.offset3 - .alert.alert-info(style="color: #333") - h3 Want to use ShareLaTeX for free? - p Tell your university or department about our - a(href="/university") site licenses - | and use of all our - | features for free as a student or member of staff. - - include ../general/small-footer - link(rel='stylesheet', href='/brand/plans.css?fingerprint='+fingerprint('/brand/plans.css')) + .col-md-12 + .page-header.plans-header.plans-subheader.text-centered + h2 Enjoy all of these great features + .col-md-4 + .card.features.text-centered + i.fa.fa-file-text-o.fa-5x + h4 Unlimited projects + p Create as much as you like. + .col-md-4 + .card.features.text-centered + i.fa.fa-clock-o.fa-5x + h4 Full document history + p Never lose a step, we've got your back. + .col-md-4 + .card.features.text-centered + i.fa.fa-dropbox.fa-5x + h4 Sync to Dropbox + p Access your projects everywhere. + .row(ng-if="ui.view == 'monthly' || ui.view == 'annual'", ng-cloak) + .col-md-12 + .page-header.plans-header.plans-subheader.text-centered + h2 Group Plans + p Improve the workflow of your research group by using ShareLaTeX with everyone on your team + .row(ng-if="ui.view == 'monthly' || ui.view == 'annual'", ng-cloak) + .col-md-12 + .card-group.text-centered + .card + .card-header + h2 Group + .circle + span(ng-if="ui.view == 'monthly'") + | $50 + span.small / mo + span(ng-if="ui.view == 'annual'") + | $500 + span.small / yr + ul.list-unstyled + li 5 group members + li.small All the advantages of the Professional account for each team member + li + br + a.btn.btn-primary(href="/user/subscription/new?planCode=group_5_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! + .card.highlighted + .card-header + h2 Research Lab + .circle + span(ng-if="ui.view == 'monthly'") + | $90 + span.small / mo + span(ng-if="ui.view == 'annual'") + | $900 + span.small / yr + ul.list-unstyled + li 10 group members + li.small All the advantages of the Professional account for each team member + li + br + a.btn.btn-primary(href="/user/subscription/new?planCode=group_10_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! + .card + .card-header + h2 Department + .circle + span(ng-if="ui.view == 'monthly'") + | $170 + span.small / mo + span(ng-if="ui.view == 'annual'") + | $1700 + span.small / yr + ul.list-unstyled + li 20 group members + li.small All the advantages of the Professional account for each team member + li + br + a.btn.btn-primary(href="/user/subscription/new?planCode=group_20_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! diff --git a/services/web/public/coffee/app/main.coffee b/services/web/public/coffee/app/main.coffee index 6d0579dc95..452c661b8f 100644 --- a/services/web/public/coffee/app/main.coffee +++ b/services/web/public/coffee/app/main.coffee @@ -2,6 +2,7 @@ define [ "main/project-list" "main/user-details" "main/account-settings" + "main/plans" "directives/asyncForm" "directives/stopPropagation" "directives/focus" diff --git a/services/web/public/coffee/app/main/plans.coffee b/services/web/public/coffee/app/main/plans.coffee new file mode 100644 index 0000000000..f5a3eb8945 --- /dev/null +++ b/services/web/public/coffee/app/main/plans.coffee @@ -0,0 +1,6 @@ +define [ + "base" +], (App) -> + App.controller "PlansController", ($scope) -> + $scope.ui = + view: "monthly" \ No newline at end of file diff --git a/services/web/public/stylesheets/app/plans.less b/services/web/public/stylesheets/app/plans.less new file mode 100644 index 0000000000..edd7753d6d --- /dev/null +++ b/services/web/public/stylesheets/app/plans.less @@ -0,0 +1,124 @@ +.plans { + p { + color: @gray-dark; + margin-bottom: @line-height-computed; + } + .plans-header{ + h1, h2, p { + text-shadow: 0 -1px 1px white; + } + h1, h2 { + color: @red; + } + p { + margin-bottom: 0; + } + } + .plans-subheader { + margin-bottom: @line-height-computed; + } + .card.features { + margin-top: @line-height-computed; + i { + color: @red; + } + p { + margin: 0; + } + } + .card { + background-color: white; + border-radius: 3px; + -webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.1); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin: @line-height-computed 0; + padding: @line-height-computed; + .card-header { + border-bottom: 1px solid @gray-lighter; + padding-bottom: @line-height-computed; + margin-bottom: @line-height-computed; + h2 { + margin: 0; + } + } + .circle { + font-size: 1.5rem; + font-weight: 700; + padding: 38px 18px; + margin: 0 auto @line-height-computed; + text-shadow: 0 -1px 1px darken(@link-color, 10%); + width: 120px; + height: 120px; + small { + margin-top: @line-height-computed / 2; + } + } + &.first { + margin-top: 0; + } + &.card-thin { + padding: @line-height-computed / 4; + } + } + + .card-group { + margin: @line-height-computed 0; + .card { + border-radius: 0; + display: inline-block; + margin: 0; + &:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + } + &:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + } + width: 300px; + } + .highlighted { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + margin-top: -@line-height-computed * 2; + padding-top: @line-height-computed * 2; + padding-bottom: @line-height-computed * 2; + position: relative; + z-index: 10; + } + } + > .card-group > .card { + padding-left: 1rem; + padding-right: 1rem; + } + .circle { + border-radius: 50%; + background-color: @red; + color: white; + padding: @line-height-computed / 2; + margin-bottom: @line-height-computed; + white-space: nowrap; + span.small { + color: rgba(255, 255, 255, 0.75); + font-size: @font-size-base * .8; + } + } + ul.nav-pills { + text-align: center; + margin-bottom: @line-height-computed; + li { + float: none; + display: inline-block; + a { + background-color: darken(@gray-lightest, 5%); + } + &.active { + a { + background-color: @link-color; + } + } + } + > li + li { + margin-left: @line-height-computed / 2; + } + } +} \ No newline at end of file diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less index e0eb3f7acb..d0123c0653 100755 --- a/services/web/public/stylesheets/style.less +++ b/services/web/public/stylesheets/style.less @@ -58,3 +58,4 @@ @import "app/project-list.less"; @import "app/editor.less"; @import "app/homepage.less"; +@import "app/plans.less"; From 7947eb28adb5bdbde788fec1c311f83f991de123 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 12:30:19 +0100 Subject: [PATCH 2/9] Remove ab test from plans page --- .../SubscriptionController.coffee | 7 - .../views/subscriptions/plans-freetrial.jade | 168 ------------------ 2 files changed, 175 deletions(-) delete mode 100644 services/web/app/views/subscriptions/plans-freetrial.jade diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 784c9ee713..78b97ed166 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -14,17 +14,10 @@ module.exports = SubscriptionController = plansPage: (req, res, next) -> plans = SubscriptionViewModelBuilder.buildViewModel() - if !req.session.user? - for plan in plans - plan.href = "/register?redir=#{plan.href}" viewName = "subscriptions/plans" - if req.query.variant? - viewName += "-#{req.query.variant}" logger.log viewName:viewName, "showing plans page" res.render viewName, title: "Plans and Pricing" - plans: plans - gaExperimentCode: gaExperimentCode #get to show the recurly.js page diff --git a/services/web/app/views/subscriptions/plans-freetrial.jade b/services/web/app/views/subscriptions/plans-freetrial.jade deleted file mode 100644 index b4a5a4f01f..0000000000 --- a/services/web/app/views/subscriptions/plans-freetrial.jade +++ /dev/null @@ -1,168 +0,0 @@ -extends ../layout - -mixin liSection(feature) - | #{feature.text} - -if(feature.comingSoon) - span.label.label-info coming soon - -if(feature.beta) - span.label.label-warning beta - - -mixin plan(plan, cssClass, monthly) - .pricing-table - ul(class=cssClass) - - li.pricing-header-row-1 - .package-title - h2.no-bold #{plan.name} - - li.pricing-header-row-2 - .package-price - if plan.price == 0 - h1.free Free forever - else - h1.no-bold - | $#{plan.price/100} - if monthly - span.cents /month - else - span.cents /year - - - var odd = false - - if(plan.planCode != "personal") - li.pricing-content-row-even - mixin liSection({text:"30 day free trial"}) - -each feature in plan.featureDescription - - odd = !odd - - if(odd) - li.pricing-content-row-odd - mixin liSection(feature) - - else - li.pricing-content-row-even - mixin liSection(feature) - - if(plan.planCode == "personal") - li.pricing-content-row-even - mixin liSection({text:""}) - - - li.pricing-footer - - var href = '/user/subscription/new?planCode='+plan.planCode - - - planIsPersonal = plan.planCode.indexOf("personal") != -1 - - userNotLoggedIn = session && !session.user - -if(planIsPersonal) - - href = "/register" - -else if(userNotLoggedIn) - - href = "/register?redir="+href - a.btn.btn-success(href='#{href}').sign_up_now - | Sign Up Now! - - -block content - .container - .row - .span12.span-box - .page-header - h1 Choose your plan - blockquote.quote.pull-right - p - | This is one of the most useful resources I have ever found on the Internet. - br - | Fantastic execution and thoughtful attention to detail make this product shine! - small Benjamin Shepherd, Waterloo University - - .row - .span12 - .offset3 - ul.nav.nav-pills.pricing-pills - li.active - a(href="#", data-target=".monthly-pricing", data-toggle="tab") Monthly - li - a(href="#", data-target=".annual-pricing", data-toggle="tab") Annual - li - a(href="#", data-target=".student-pricing", data-toggle="tab") Half price student plans - - .row - .span12 - .page-header - h2 Individual Plans - - .pricing-steelblue.pricing-row - .tab-content - .tab-pane.active.monthly-pricing - .row - .span6.offset3.tagline.freeTrialBlurb - h1 Start your risk free 30 day trial - .row - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['collaborator_free_trial'], "big", true) - .span4 - mixin plan(plans.allPlans['professional_free_trial'], "", true) - - .tab-pane.annual-pricing - .row - .span6.offset3.tagline.freeTrialBlurb - h1 Start your risk free 30 day trial - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['collaborator-annual_free_trial'], "big", false) - .span4 - mixin plan(plans.allPlans['prof-ann_free_trial'], "", false) - - .tab-pane.student-pricing - .row - .span6.offset3.tagline.freeTrialBlurb - h1 Start your risk free 30 day trial - .span4 - mixin plan(plans.personalAccount, "", true) - .span4 - mixin plan(plans.allPlans['student_free_trial'], "big", true) - .span4 - mixin plan(plans.allPlans['stud-ann_free_trial'], "", false) - - .pricing-steelblue.pricing-row - .tab-content - .tab-pane.active.monthly-pricing - .page-header - h2 Group Plans - .row - .span12.tagline - p Improve the workflow of your research group by unlocking ShareLaTeX's premium features for everyone on your team - .row - .span4 - mixin plan(plans.groupMonthlyPlans[0], "", true) - .span4 - mixin plan(plans.groupMonthlyPlans[1], "big", true) - .span4 - mixin plan(plans.groupMonthlyPlans[2], "", true) - .tab-pane.annual-pricing - .page-header - h2 Group Plans - .row - .span12.tagline - p Improve the workflow of your research group by unlocking ShareLaTeX's premium features for everyone on your team - .row - .span4 - mixin plan(plans.groupAnnualPlans[0], "", false) - .span4 - mixin plan(plans.groupAnnualPlans[1], "big", false) - .span4 - mixin plan(plans.groupAnnualPlans[2], "", false) - .tab-pane.student-pricing - - - .row - .span6.offset3 - .alert.alert-info(style="color: #333") - h3 Want to use ShareLaTeX for free? - p Tell your university or department about our - a(href="/university") site licenses - | and use of all our - | features for free as a student or member of staff. - - include ../general/small-footer - link(rel='stylesheet', href='/brand/plans.css?fingerprint='+fingerprint('/brand/plans.css')) - From 5f48105b041291d19f9fceb907309dd82b274da2 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 13:43:36 +0100 Subject: [PATCH 3/9] Style subscription management pages --- services/web/app/views/external/about.jade | 4 +- .../app/views/subscriptions/dashboard.jade | 100 ++- .../subscriptions/edit-billing-details.jade | 14 +- services/web/app/views/subscriptions/new.jade | 14 +- .../successful_subscription.jade | 61 +- services/web/public/recurly/recurly.css | 1 - .../public/stylesheets/app/about-page.less | 6 - services/web/public/stylesheets/app/base.less | 12 + .../web/public/stylesheets/app/recurly.less | 781 ++++++++++++++++++ services/web/public/stylesheets/style.less | 15 +- 10 files changed, 903 insertions(+), 105 deletions(-) create mode 100644 services/web/public/stylesheets/app/recurly.less diff --git a/services/web/app/views/external/about.jade b/services/web/app/views/external/about.jade index 4e09b2419f..49f767350d 100644 --- a/services/web/app/views/external/about.jade +++ b/services/web/app/views/external/about.jade @@ -9,7 +9,7 @@ block content h1 About us h2 Meet the team behind your favourite online LaTeX editor. p.team-profile - span.img-container + span.img-container.img-circle img(src='/img/about/henry_oswald.jpg') strong Henry Oswald | built an experimental LaTeX editor in 2011 which later became ShareLaTeX. He is a trained software engineer who lives in London. @@ -18,7 +18,7 @@ block content a(href='https://twitter.com/henryoswald') Follow me on Twitter | . p.team-profile - span.img-container + span.img-container.img-circle img(src='/img/about/james_allen.jpg') strong James Allen | started working with Henry early in 2012 and finished his PhD in theoretical physics early in 2013. James began working on diff --git a/services/web/app/views/subscriptions/dashboard.jade b/services/web/app/views/subscriptions/dashboard.jade index de61a99317..7b5bc077b1 100644 --- a/services/web/app/views/subscriptions/dashboard.jade +++ b/services/web/app/views/subscriptions/dashboard.jade @@ -7,12 +7,6 @@ mixin printPlan(plan) tr td strong #{plan.name} - td - ul - -for benefit in plan.featureDescription - li #{benefit.text}   - if benefit.comingSoon - span.label.label-info coming soon td -if (plan.annual) | $#{plan.price / 100} / year @@ -20,67 +14,71 @@ mixin printPlan(plan) | $#{plan.price / 100} / month td -if (subscription.state == "free-trial") - a(href="/user/subscription/new?planCode=#{plan.planCode}").btn.btn-primary Subscribe to this plan + a(href="/user/subscription/new?planCode=#{plan.planCode}").btn.btn-success Subscribe to this plan -else if (plan.planCode == subscription.planCode) button.btn.disabled Your plan -else form(action="/user/subscription/update",method="post") input(type="hidden", name="_csrf", value=csrfToken) input(type="hidden",name="plan_code",value="#{plan.planCode}") - input(type="submit",value="Change to this plan").btn.btn-primary + input(type="submit",value="Change to this plan").btn.btn-success mixin printPlans(plans) -each plan in plans mixin printPlan(plan) block content - include ../general/sidebar + .content.content-alt + .container + .row + .col-md-8.col-md-offset-2 + .card + .page-header + h1 Your Subscription + case subscription.state + when "free-trial" + p You are currently using a free trial which expires on #{subscription.expiresAt}. + p Choose a plan below to subscribe to. + when "active" + p You are currently subscribed to the #{subscription.name} plan. + a(href, ng-click="changePlan = true") Change Plan. + p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt} + p.pull-right - .content-with-navigation-sidebar - .box - .row-fluid - .span12 - .page-header - h1 Your Subscription - p: case subscription.state - when "free-trial" - p You are currently using a free trial which expires on #{subscription.expiresAt}. - p Choose a plan below to subscribe to. - when "active" - p You are currently subscribed to the #{subscription.name} plan. - p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt} - form(action="/user/subscription/cancel",method="post") - input(type="hidden", name="_csrf", value=csrfToken) - input(type="submit",value="Cancel your subscription").btn.btn-danger#cancelSubscription - p: a(href="/user/subscription/billing-details/edit").btn.btn-primary Update your billing details - when "canceled" - p You are currently subscribed to the #{subscription.name} plan. - p Your subscription has been canceled and will terminate on #{subscription.nextPaymentDueAt}. No further payments will be taken. - form(action="/user/subscription/reactivate",method="post") - input(type="hidden", name="_csrf", value=csrfToken) - input(type="submit",value="Reactivate your subscription").btn.btn-success - when "expired" - p Your subscription has expired. - a(href="/user/subscription/plans") Create New Subscription - default - p There is a problem with your subscription. Please contact us for more information. + p: form(action="/user/subscription/cancel",method="post") + input(type="hidden", name="_csrf", value=csrfToken) + a(href="/user/subscription/billing-details/edit").btn.btn-info Update your billing details + |   + input(type="submit", value="Cancel your subscription").btn.btn-primary#cancelSubscription + when "canceled" + p You are currently subscribed to the #{subscription.name} plan. + p Your subscription has been canceled and will terminate on #{subscription.nextPaymentDueAt}. No further payments will be taken. + p: form(action="/user/subscription/reactivate",method="post") + input(type="hidden", name="_csrf", value=csrfToken) + input(type="submit",value="Reactivate your subscription").btn.btn-success + when "expired" + p Your subscription has expired. + a(href="/user/subscription/plans") Create New Subscription + default + p There is a problem with your subscription. Please contact us for more information. - -if(subscription.groupPlan) - a(href="/subscription/group").btn.btn-success Manage Group - hr - h2 Change plan - p: table.table - tr - th Name - th Features - th Price - th - mixin printPlans(plans.studentAccounts) - mixin printPlans(plans.individualMonthlyPlans) - mixin printPlans(plans.individualAnnualPlans) + -if(subscription.groupPlan) + a(href="/subscription/group").btn.btn-success Manage Group + + div(ng-show="changePlan", ng-cloak) + hr + h2 Change plan + p: table.table + tr + th Name + th Price + th + mixin printPlans(plans.studentAccounts) + mixin printPlans(plans.individualMonthlyPlans) + mixin printPlans(plans.individualAnnualPlans) - script(type="text/javascript") + script(type="text/javascript"). $('#cancelSubscription').on("click", function() { ga('send', 'event', 'subscription-funnel', 'cancelation') }) diff --git a/services/web/app/views/subscriptions/edit-billing-details.jade b/services/web/app/views/subscriptions/edit-billing-details.jade index 42f3f0de88..b3fa4114d9 100644 --- a/services/web/app/views/subscriptions/edit-billing-details.jade +++ b/services/web/app/views/subscriptions/edit-billing-details.jade @@ -4,11 +4,18 @@ block content - locals.supressDefaultJs = true script(data-main=jsPath+'main.js', src=jsPath+'libs/require.js', baseurl=jsPath) script(src=jsPath+'libs/recurly.min.js') - link(rel='stylesheet', href='/recurly/recurly.css') - #billingDetailsForm.box Loading billing details form... + .content.content-alt + .container + .row + .col-md-6.col-md-offset-3 + .card + .page-header + h1.text-centered Update Your Billing Details + #billingDetailsForm Loading billing details form... + - script(type="text/javascript") + script(type="text/javascript"). Recurly.config(!{recurlyConfig}) Recurly.buildBillingInfoUpdateForm({ target : "#billingDetailsForm", @@ -17,6 +24,5 @@ block content accountCode : "#{user.id}" }); - include ../general/small-footer diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index f23954c9d0..9852db25f8 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -4,15 +4,21 @@ block content - locals.supressDefaultJs = true script(data-main=jsPath+'main.js', src=jsPath+'libs/require.js', baseurl=jsPath) script(src=jsPath+'libs/recurly.min.js') - link(rel='stylesheet', href='/recurly/recurly.css') - #subscribeForm.box Loading subscription form... + .content.content-alt + .container + .row + .col-md-6.col-md-offset-3 + .card + .page-header + h1.text-centered New Subscription + #subscribeForm Loading subscription form... script(type="text/javascript") ga('send', 'event', 'pageview', 'payment_form', "#{plan_code}") - script(type="text/javascript") + script(type="text/javascript"). Recurly.config(!{recurlyConfig}) var recurlySubscriptionFormConfig = !{subscriptionFormOptions} recurlySubscriptionFormConfig.successHandler = function(){ @@ -22,5 +28,3 @@ block content Recurly.buildSubscriptionForm(recurlySubscriptionFormConfig); - include ../general/small-footer - diff --git a/services/web/app/views/subscriptions/successful_subscription.jade b/services/web/app/views/subscriptions/successful_subscription.jade index 5a8f249f44..1e1fd24b3e 100644 --- a/services/web/app/views/subscriptions/successful_subscription.jade +++ b/services/web/app/views/subscriptions/successful_subscription.jade @@ -1,33 +1,36 @@ extends ../layout block content - link(href='http://fonts.googleapis.com/css?family=Just+Another+Hand', rel='stylesheet', type='text/css') - .container - .row - div   - .span8.offset2.span-box - .page-header - h2 Thanks for subscribing! - .alert.alert-success - p Your card will be charged soon. - p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt}, if you do not want to be charged again - a(href="/user/subscription") click here to cancel. - div - p - - if (subscription.groupPlan == true) - a.btn.btn-success.btn-large(href="/subscription/group") Add your first group members now - div.letter-from-founders - p Thank you for subscribing to the #{subscription.name} plan. It's support from people like yourself that allows ShareLaTeX to continue to grow and improve. + .content.content-alt + .container + .row + .col-md-8.col-md-offset-2 + .card + .page-header + h2 Thanks for subscribing! + .alert.alert-success + p Your card will be charged soon. + p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt}. + p If you do not want to be charged again + a(href="/user/subscription") click here to cancel. - p If there is anything you ever need please feel free to contact us directly at - a(href='mailto:support@sharelatex.com') support@sharelatex.com - | - it goes straight to both our inboxes. - p Regards, - br - | Henry and James - .portraits - img(src="/img/about/henry_oswald.jpg") -   - img(src="/img/about/james_allen.jpg") - div - a.btn.btn-primary(href="/project") < Back to your projects + p + - if (subscription.groupPlan == true) + a.btn.btn-success.btn-large(href="/subscription/group") Add your first group members now + p.letter-from-founders + p Thank you for subscribing to the #{subscription.name} plan. It's support from people like yourself that allows ShareLaTeX to continue to grow and improve. + + p If there is anything you ever need please feel free to contact us directly at + a(href='mailto:support@sharelatex.com') support@sharelatex.com + | . It goes straight to both our inboxes. + p Regards, + br + | Henry and James + .portraits + span.img-circle + img(src="/img/about/henry_oswald.jpg") +   + span.img-circle + img(src="/img/about/james_allen.jpg") + p + a.btn.btn-primary(href="/project") < Back to your projects diff --git a/services/web/public/recurly/recurly.css b/services/web/public/recurly/recurly.css index 60ec33fab7..b20e73bef3 100755 --- a/services/web/public/recurly/recurly.css +++ b/services/web/public/recurly/recurly.css @@ -1,7 +1,6 @@ .recurly { display: block; position: relative; - width: 500px; } .recurly .cost, .recurly .discount { diff --git a/services/web/public/stylesheets/app/about-page.less b/services/web/public/stylesheets/app/about-page.less index c73e038e50..651e71ca53 100644 --- a/services/web/public/stylesheets/app/about-page.less +++ b/services/web/public/stylesheets/app/about-page.less @@ -1,14 +1,8 @@ .team-profile { clear: both; .img-container { - border-radius: 50%; float: left; - height: @line-height-computed * 4; overflow: hidden; margin: (@line-height-computed / 2) @line-height-computed @line-height-computed (@line-height-computed / 2); - width: @line-height-computed * 4; - } - img { - margin-top: -10px; } } \ No newline at end of file diff --git a/services/web/public/stylesheets/app/base.less b/services/web/public/stylesheets/app/base.less index c1de335fbb..9203300aa4 100644 --- a/services/web/public/stylesheets/app/base.less +++ b/services/web/public/stylesheets/app/base.less @@ -1,3 +1,15 @@ [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; +} + + +.img-circle { + display: inline-block; + overflow: hidden; + border-radius: 50%; + width: @line-height-computed * 4; + height: @line-height-computed * 4; + img { + margin-top: -10px; + } } \ No newline at end of file diff --git a/services/web/public/stylesheets/app/recurly.less b/services/web/public/stylesheets/app/recurly.less new file mode 100644 index 0000000000..bdeb5bc336 --- /dev/null +++ b/services/web/public/stylesheets/app/recurly.less @@ -0,0 +1,781 @@ +.recurly { + display: block; + position: relative; + box-sizing: content-box; + * { + box-sizing: content-box; + } + .due_now { + display: none; + } +} +.recurly .cost, +.recurly .discount { + font-size: 16px; + text-align: right; +} +.recurly .subscription { + border-radius: 9px 9px 0 0; + text-shadow: 0 1px 0 #fff; + padding-top: 20px; + overflow: hidden; +} +.recurly .plan { + color: #333; + overflow: hidden; + position: relative; + zoom: 1; + font-family: @font-family-serif; +} +.recurly .plan .name { + float: left; + font-size: 32px; + min-width: 200px; + padding-left: 20px; + padding-right: 40px; +} +.recurly .plan .quantity.field { + clear: none; + width: 60px; + margin: 4px 0; +} +.recurly .plan .quantity.field input[type=text] { + width: 48px; +} +.recurly .plan .quantity.field:before { + content: "\d7"; + height: 48px; + line-height: 30px; + position: absolute; + right: 100%; + width: 40px; + font-size: 20px; + text-align: center; + vertical-align: middle; + z-index: 1337; + color: #666; +} +.recurly .plan .recurring_cost { + float: right; + text-align: right; + padding-right: 20px; +} +.recurly .plan .recurring_cost .cost { + font-size: 32px; +} +.recurly .plan .recurring_cost .interval { + font-size: 12px; + padding-bottom: 20px; +} +.recurly .free_trial { + clear: left; + float: left; + font-size: 13px; + height: 22px; + margin: 0; + position: absolute; + top: 35px; + left: 20px; + font-style: italic; +} +.recurly .setup_fee { + clear: both; + background: url("/recurly/images/dash.png") repeat-x 1px top; + overflow: hidden; + padding-top: 20px; +} +.recurly .setup_fee .title { + float: left; + padding-left: 20px; + font-weight: bold; + font-size: 16px; +} +.recurly .setup_fee .cost { + float: right; + padding-right: 20px; +} +.recurly .vat { + height: 24px; + padding: 20px 20px; + display: none; + background: url("/recurly/images/dash.png") repeat-x 1px top; +} +.recurly .vat.applicable { + display: block; +} +.recurly .vat .title { + font-size: 16px; + font-weight: normal; + float: left; +} +.recurly .vat .cost { + float: right; + font-size: 18px; +} +.recurly .add_ons { + clear: both; +} +.recurly .add_ons.any { + margin: 20px 10px; +} +.recurly .add_ons .add_on { + background: #ecedee; + background: -webkit-linear-gradient(top, #ecedee, #e5e6e7); + background: -moz-linear-gradient(top, #ecedee, #e5e6e7); + background: -o-linear-gradient(top, #ecedee, #e5e6e7); + background: linear-gradient(top, #ecedee, #e5e6e7); + margin: 0; + height: 43px; + line-height: 42px; + vertical-align: middle; + position: relative; + clear: both; + overflow: hidden; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + text-shadow: 0 1px 0 #fff; + color: #999; + font-weight: 300; + font-size: 16px; + zoom: 1; + cursor: default; +} +.recurly .add_ons .add_on.first { + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} +.recurly .add_ons .add_on.last { + border-bottom: 1px solid #ccc; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} +.recurly .add_ons .add_on .name { + font-size: inherit; + font-weight: inherit; + font-style: italic; + color: inherit; + width: 200px; + margin-left: 9px; + margin-right: 20px; + position: absolute; + left: 0; + top: 0; +} +.recurly .add_ons .add_on .quantity.field { + position: absolute; + top: 4px; + left: 249px; + width: 60px; + display: none; +} +.recurly .add_ons .add_on .quantity.field input[type=text] { + width: 48px; +} +.recurly .add_ons .add_on .quantity.field:before { + content: "\d7"; + height: 48px; + line-height: 30px; + position: absolute; + right: 100%; + width: 40px; + font-size: 20px; + text-align: center; + vertical-align: middle; + z-index: 1337; + color: #666; +} +.recurly .add_ons .add_on .cost { + font-size: inherit; + line-height: inherit; + vertical-align: middle; + position: absolute; + right: 10px; +} +.recurly .add_ons .add_on:hover { + background: -webkit-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); + background: -moz-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); + background: -o-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); + background: linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); + box-shadow: inset 0 1px 0 #fff; + text-shadow: none; + color: #111; +} +.recurly .add_ons .add_on:active, +.recurly .add_ons .add_on.selected { + color: #111; + background: -webkit-linear-gradient(top, #f0f0f0, #fff); + background: -moz-linear-gradient(top, #f0f0f0, #fff); + background: -o-linear-gradient(top, #f0f0f0, #fff); + background: linear-gradient(top, #f0f0f0, #fff); + width: auto; + box-shadow: inset 0 1px 4px 0 rgba(0,0,0,0.07); + text-shadow: none; +} +.recurly .add_ons .add_on.selected { + background: #fff url("/recurly/images/check.png") no-repeat 10px center; +} +.recurly .add_ons .add_on.selected .name { + padding-left: 24px; +} +.recurly .add_ons .add_on.selected:hover { + background: #fcf5f0 url("/recurly/images/uncheck.png") no-repeat 10px center; +} +.recurly .add_ons .add_on.selected .quantity { + display: block; +} +.recurly .coupon { + clear: both; + overflow: hidden; + height: 34px; + color: #333; + padding: 20px 20px; + position: relative; + background: url("/recurly/images/dash.png") repeat-x 1px top; +} +.recurly .coupon .check { + width: 26px; + height: 26px; + float: left; + border-radius: 15px 15px 15px 15px; + background: #70ccf8; + border: 1px solid #0090c9; + margin: 3px 0 1px 10px; + box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.35), 0 1px 1px 0 rgba(0,0,0,0.10); + background: #43bef9 url("/recurly/images/coupon_check.png") no-repeat center center; + background: url("/recurly/images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); +} +.recurly .coupon .check:hover { + background: url("/recurly/images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + background: url("/recurly/images/coupon_check.png") no-repeat center center linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); + box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.75), 0 1px 1px 0 rgba(0,0,0,0.10); +} +.recurly .coupon .check:active { + background: url("/recurly/images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #f0f0f0, #fff); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #f0f0f0, #fff); + background: url("/recurly/images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #f0f0f0, #fff); + background: url("/recurly/images/coupon_check.png") no-repeat center center linear-gradient(top, #f0f0f0, #fff); + box-shadow: inset 0 3px 3px 0 rgba(0,0,0,0.03); + border: 1px solid #999; +} +.recurly .coupon.checking .check { + background: #f0f0f0 url("/recurly/images/coupon_checking.gif") no-repeat center center; + box-shadow: inset 0 3px 3px 0 rgba(0,0,0,0.03); + border: 1px solid #999; +} +.recurly .coupon.invalid .coupon_code { + border-color: #a55; + background: #fee; + color: #311; +} +.recurly .coupon .coupon_code .error { + left: 300px; +} +.recurly .coupon .description { + float: left; + margin-left: 20px; + height: 34px; + line-height: 34px; + vertical-align: middle; + font-size: 14.4px; +} +.recurly .coupon .discount { + float: right; + height: 34px; + line-height: 34px; + vertical-align: middle; +} +.recurly .error { + padding: 5px; + line-height: 22px; + vertical-align: middle; + color: #000; + text-shadow: 0 1px 0 #fec; + background: #ffc; + border: 1px solid #ba1; + box-shadow: 3px 5px 5px 0 rgba(0,0,0,0.10); + border-radius: 5px; + font-size: 13px; +} +.recurly .server_errors { + color: #fff; + text-shadow: 0 1px 0 #000; + margin: 0 20px; + opacity: 0; +} +.recurly .server_errors .error { + padding-left: 26px; + background: rgba(240,250,0,0.50) url("/recurly/images/error.png") no-repeat 5px 9px; +} +.recurly .server_errors.any { + opacity: 1; + -webkit-transition: opacity 0.5s linear; + -moz-transition: opacity 0.5s linear; + margin: 20px 20px; + margin-bottom: 0; +} +.recurly .contact_info, +.recurly .billing_info, +.recurly .accept_tos { + position: relative; + padding: 20px 20px; + overflow: hidden; + zoom: 1; +} +.recurly .title { + font-size: 16px; + height: 20px; + font-weight: bold; + padding-bottom: 20px; + color: #404041; + text-shadow: 0 1px 0 #fff; + float: left; +} +.recurly .credit_card, +.recurly .paypal { + clear: both; +} +.recurly .payment_method { + margin-bottom: 20px; + width: 300px; +} +.recurly .payment_method .payment_option { + float: right; +} +.recurly .payment_method .payment_option input[type=radio] { + margin-right: 10px; + display: none; +} +.recurly .payment_method.multiple { + height: 34px; + clear: both; +} +.recurly .payment_method.multiple input[type=radio] { + display: block; + float: left; + height: 29px; +} +.recurly .payment_method.multiple .card_option { + float: left; +} +.recurly .payment_method.multiple .paypal_option { + float: right; +} +.recurly .payment_method.multiple .logo, +.recurly .payment_method.multiple .accepted_cards { + opacity: 0.5; +} +.recurly .payment_method.multiple .payment_option:hover .logo, +.recurly .payment_method.multiple .payment_option.selected .logo, +.recurly .payment_method.multiple .payment_option:hover .accepted_cards, +.recurly .payment_method.multiple .payment_option.selected .accepted_cards { + cursor: pointer; + opacity: 1; +} +.recurly .payment_method .payment_option { + line-height: 32px; + display: block; + height: 32px; + line-height: 34px; +} +.recurly .payment_method .payment_option .title { + margin: 0; + font-size: 12px; +} +.recurly .payment_method .payment_option .icon { + float: left; + width: 24px; + height: 34px; + margin: 0 5px; +} +.recurly .payment_method .payment_option.card_option { + border-radius: 5px 0 0 5px; + border-right: none; +} +.recurly .payment_method .payment_option.card_option .icon { + width: 42px; + background: url("/recurly/images/credit_cards/generic.png") no-repeat 0 center; +} +.recurly .payment_method .payment_option.paypal_option { + border-radius: 0 5px 5px 0; + float: right; +} +.recurly .payment_method .payment_option.paypal_option .logo { + height: 24px; + width: 90px; + display: inline-block; + vertical-align: middle; + background: url("/recurly/images/paypal_logo.png") no-repeat 0 center; +} +.recurly .paypal_message { + width: 300px; + font-style: italic; + margin-bottom: 10px; +} +.recurly .contact_info { + background: url("/recurly/images/dash.png") repeat-x 1px bottom; +} +.recurly .accept_tos { + background: url("/recurly/images/dash.png") repeat-x 1px top; + overflow: visible; +} +.recurly .accept_tos input[type=checkbox] { + display: inline; + line-height: 34px; + vertical-align: middle; +} +.recurly .accept_tos label { + margin: 0 0 0 5px; + display: inline; + line-height: 34px; + vertical-align: middle; +} +.recurly .accept_tos .field .error { + display: block; + position: static; +} +.recurly .field { + display: inline; + float: left; + clear: left; + width: 300px; + height: 34px; + margin-bottom: 20px; + position: relative; +} +.recurly .field input[type=text] { + width: 288px; +} +.recurly .field.company_name { + margin-bottom: 0; +} +.recurly .field .error { + min-width: 128px; + white-space: nowrap; + position: absolute; + top: 0; + left: 100%; + margin-left: 20px; + z-index: 1337; +} +.recurly .field .placeholder { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding-left: 9px; + font-size: 16px; + font-weight: normal; + line-height: 34px; + vertical-align: middle; + color: #999; + cursor: text; + overflow: hidden; + white-space: nowrap; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + font-weight: 300; +} +.recurly .field.focus .placeholder { + color: #ccc; +} +.recurly .field.invalid .placeholder { + color: #a77; +} +.recurly .field.coupon_code { + width: 140px; +} +.recurly .field.coupon_code input[type=text] { + width: 128px; +} +.recurly .field.first_name { + clear: left; + width: 140px; +} +.recurly .field.first_name input[type=text] { + width: 128px; +} +.recurly .field.first_name .error { + left: 300px; +} +.recurly .field.last_name { + width: 140px; + margin-left: 20px; + margin-left: 20px; + clear: none; + clear: none; +} +.recurly .field.last_name input[type=text] { + width: 128px; +} +.recurly .field.card_number { + width: 220px; +} +.recurly .field.card_number input[type=text] { + width: 208px; +} +.recurly .field.card_number .error { + left: 300px; +} +.recurly .field.cvv { + width: 60px; + margin-left: 20px; + margin-left: 20px; + clear: none; + clear: none; +} +.recurly .field.cvv input[type=text] { + width: 48px; +} +.recurly .field.expires { + width: 300px; +} +.recurly .field.expires input[type=text] { + width: 288px; +} +.recurly .field.expires .title { + float: left; + font-size: 13px; + line-height: 24px; + vertical-align: middle; + width: 59px; +} +.recurly .field.expires .month { + float: left; + width: 120px; + margin-left: 0; +} +.recurly .field.expires .month input[type=text] { + width: 108px; +} +.recurly .field.expires .year { + float: left; + margin-left: 1px; + width: 60px; +} +.recurly .field.expires .year input[type=text] { + width: 48px; +} +.recurly .field.state { + width: 180px; +} +.recurly .field.state input[type=text] { + width: 168px; +} +.recurly .field.state .error { + left: 300px; +} +.recurly .field.zip { + width: 100px; + margin-left: 20px; + margin-left: 20px; + clear: none; + clear: none; +} +.recurly .field.zip input[type=text] { + width: 88px; +} +.recurly .field.vat_number { + width: 140px; + display: none; +} +.recurly .field.vat_number input[type=text] { + width: 128px; +} +.recurly .field.vat_number.applicable { + display: block; +} +.recurly .only_zipstreet .zip.field, +.recurly .only_zip .zip.field { + margin-left: 0; + clear: left; +} +.recurly .accepted_cards { + display: inline-block; + height: 34px; + overflow: hidden; +} +.recurly .card { + background-position: right top; + background-repeat: no-repeat; + text-indent: -3000px; + width: 32px; + height: 32px; + margin: 0; + padding: 0; + display: inline-block; +} +.recurly .card.mastercard { + background-image: url("/recurly/images/credit_cards/mastercard.png"); +} +.recurly .card.american_express { + background-image: url("/recurly/images/credit_cards/american_express.png"); +} +.recurly .card.visa { + background-image: url("/recurly/images/credit_cards/visa.png"); +} +.recurly .card.discover { + background-image: url("/recurly/images/credit_cards/discover.png"); +} +.recurly .card.jcb { + background-image: url("/recurly/images/credit_cards/jcb.png"); +} +.recurly .card.laser { + background-image: url("/recurly/images/credit_cards/laser.png"); +} +.recurly .card.diners_club { + background-image: url("/recurly/images/credit_cards/diners_club.png"); +} +.recurly .card.maestro { + background-image: url("/recurly/images/credit_cards/maestro.png"); +} +.recurly .card.no_match { + opacity: 0.5; +} +.recurly input[type=text], +.recurly select { + vertical-align: middle; + color: #000; +} +.recurly input[type=text].invalid, +.recurly select.invalid { + border-color: #a55; + background: #fee; + color: #311; +} +.recurly input[type=text] { + display: block; + background: #fff; + border: 1px solid #a0a0a5; + box-shadow: inset 0 2px 3px rgba(0,0,0,0.10); + font-size: 16px; + font-family: inherit; + padding: 5px; + height: 22px; +} +.recurly input[type=text][disabled] { + background: #eee; +} +.recurly input[type=checkbox] { + color: #f00; +} +.recurly select { + color: inherit; + font-family: inherit; + width: 100%; +} +.recurly select > option { + color: inherit; +} +.recurly .due_now { + background: url("/recurly/images/due_now.png") no-repeat top left; + clear: both; + color: #2a3a3c; + height: 70px; + line-height: 67px; + vertical-align: middle; + padding: 0 25px; + width: 460px; + position: relative; + left: -5px; + text-shadow: 0 1px 0 rgba(255,255,255,0.50); +} +.recurly .due_now .title { + float: left; + font-size: 29px; + position: relative; +} +.recurly .due_now .cost { + color: #fff; + float: right; + font-size: 33px; + font-weight: bold; + letter-spacing: 1px; + margin: 0; + position: relative; + text-shadow: 0px 1px 1px rgba(0,0,0,0.90); +} +.recurly .footer { + border-radius: 0px 0px 9px 9px; + margin: 0px; + padding: 20px; +} +.recurly.submitting .footer { + background: url("/recurly/images/submitting.gif") no-repeat 180px 28px; +} +.recurly button.submit { + .btn; + .btn-lg; + .btn-primary; +} +// .recurly button.submit:hover { +// color: #451; +// } +// .recurly button.submit:active { +// top: 2px; +// color: #302106; +// text-shadow: rgba(255,255,255,0.57) 0 -1px 0; +// outline: none; +// background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ce7b00), to(#fecd00)); +// background: -moz-linear-gradient(top, #ce7b00, #fecd00); +// box-shadow: rgba(255,255,255,0.69) 0px -1px 0px inset, rgba(0,0,0,0.26) 0px 2px 3px; +// } +// .recurly button.submit[disabled] { +// position: relative; +// height: 46px; +// max-width: 600px; +// padding: 0 10px; +// font-weight: 700; +// color: #555; +// text-shadow: rgba(255,255,255,0.57) 0 1px 0; +// text-align: center; +// opacity: 0.75; +// border: 1px solid #767674; +// background: #e7a500; +// -moz-border-radius: 10px; +// -webkit-border-radius: 10px; +// border-radius: 10px; +// -webkit-user-select: none; +// -moz-user-select: -moz-none; +// outline: none; +// background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dbd9d2), to(#999)); +// background: -moz-linear-gradient(top, #dbd9d2, #999); +// -webkit-background-clip: padding-box; +// -webkit-box-shadow: rgba(255,255,255,0.69) 0px 1px 0px inset, rgba(0,0,0,0.26) 0px 2px 3px; +// box-shadow: rgba(255,255,255,0.70) 0px 1px 0px inset, rgba(0,0,0,0.27) 0px 2px 3px; +// } +.iefail { + background: #666; + padding: 10px; + position: absolute; + top: -1%; + left: -1%; + height: 102%; + width: 102%; + z-index: 9999; +} +.iefail .chromeframe { + background: #fff; + border: 1px solid #ccc; + padding: 10px; +} +.iefail .chromeframe p { + text-align: center; +} +.iefail .chromeframe p.blast { + font-size: 1.3em; + font-weight: bold; +} +.iefail .chromeframe p a { + color: #4183c4; + text-transform: capitalize; +} diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less index d0123c0653..626ac3e2fa 100755 --- a/services/web/public/stylesheets/style.less +++ b/services/web/public/stylesheets/style.less @@ -16,7 +16,7 @@ @import "components/forms.less"; @import "components/buttons.less"; @import "components/card.less"; -@import "components/code.less"; +//@import "components/code.less"; @import "components/component-animations.less"; //@import "components/glyphicons.less"; @import "components/dropdowns.less"; @@ -25,13 +25,13 @@ @import "components/navs.less"; @import "components/navbar.less"; @import "components/footer.less"; -@import "components/breadcrumbs.less"; -@import "components/pagination.less"; -@import "components/pager.less"; +//@import "components/breadcrumbs.less"; +//@import "components/pagination.less"; +//@import "components/pager.less"; @import "components/labels.less"; -@import "components/badges.less"; -@import "components/jumbotron.less"; -@import "components/thumbnails.less"; +//@import "components/badges.less"; +//@import "components/jumbotron.less"; +//@import "components/thumbnails.less"; @import "components/alerts.less"; @import "components/progress-bars.less"; @import "components/media.less"; @@ -59,3 +59,4 @@ @import "app/editor.less"; @import "app/homepage.less"; @import "app/plans.less"; +@import "app/recurly.less"; From 452098ca6867d376f9bc70c5bb65fc475602131b Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 14:05:07 +0100 Subject: [PATCH 4/9] Redirect to register if not registered on plans page --- .../SubscriptionController.coffee | 26 +++++-------------- .../web/app/views/subscriptions/plans.jade | 16 ++++++------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 78b97ed166..508656b3fc 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -14,11 +14,16 @@ module.exports = SubscriptionController = plansPage: (req, res, next) -> plans = SubscriptionViewModelBuilder.buildViewModel() + if !req.session.user? + baseUrl = "/register?redir=" + else + baseUrl = "" viewName = "subscriptions/plans" logger.log viewName:viewName, "showing plans page" res.render viewName, title: "Plans and Pricing" - + plans: plans + baseUrl: baseUrl #get to show the recurly.js page paymentPage: (req, res, next) -> @@ -161,22 +166,3 @@ module.exports = SubscriptionController = return next(error) if error? req.body = body next() - - -gaExperimentCode = ''' - - - - -''' \ No newline at end of file diff --git a/services/web/app/views/subscriptions/plans.jade b/services/web/app/views/subscriptions/plans.jade index 02520c3485..f5d38ddc37 100644 --- a/services/web/app/views/subscriptions/plans.jade +++ b/services/web/app/views/subscriptions/plans.jade @@ -11,7 +11,7 @@ block content .row .col-md-12 - p.text-centered ShareLaTeX is the world's easiest to use LaTeX editor. You'll stay up to date with your collaborators, keep track of all changes to your work, and use our up to date and fast compiling environment from anywhere in the world. + p.text-centered ShareLaTeX is the world's easiest to use LaTeX editor. You'll stay up to date with your collaborators, keep track of all changes to your work, and use our LaTeX environment from anywhere in the world. .row(ng-cloak) .col-md-12 @@ -62,7 +62,7 @@ block content li br a.btn.btn-primary( - ng-href="/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '_annual' || ''}}" + ng-href="#{baseUrl}/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '_annual' || ''}}" ) Sign up now! .card .card-header @@ -81,7 +81,7 @@ block content li br a.btn.btn-primary( - ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '_annual' || ''}}" + ng-href="#{baseUrl}/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '_annual' || ''}}" ) Sign up now! .card-group.text-centered(ng-if="ui.view == 'student'") @@ -109,7 +109,7 @@ block content li br a.btn.btn-primary( - ng-href="/user/subscription/new?planCode=student" + ng-href="#{baseUrl}/user/subscription/new?planCode=student" ) Sign up now! .card .card-header @@ -125,7 +125,7 @@ block content li br a.btn.btn-primary( - ng-href="/user/subscription/new?planCode=student_annual" + ng-href="#{baseUrl}/user/subscription/new?planCode=student_annual" ) Sign up now! .row(ng-cloak) @@ -175,7 +175,7 @@ block content li.small All the advantages of the Professional account for each team member li br - a.btn.btn-primary(href="/user/subscription/new?planCode=group_5_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! + a.btn.btn-primary(href="#{baseUrl}/user/subscription/new?planCode=group_5_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! .card.highlighted .card-header h2 Research Lab @@ -191,7 +191,7 @@ block content li.small All the advantages of the Professional account for each team member li br - a.btn.btn-primary(href="/user/subscription/new?planCode=group_10_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! + a.btn.btn-primary(href="#{baseUrl}/user/subscription/new?planCode=group_10_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! .card .card-header h2 Department @@ -207,4 +207,4 @@ block content li.small All the advantages of the Professional account for each team member li br - a.btn.btn-primary(href="/user/subscription/new?planCode=group_20_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! + a.btn.btn-primary(href="#{baseUrl}/user/subscription/new?planCode=group_20_members{{ ui.view == 'annual' && '_annual' || ''}}") Sign up now! From 41ef675999cf960d036a9f7959586645bdebb71d Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 14:58:12 +0100 Subject: [PATCH 5/9] Improve UX of user profile --- services/web/app/views/project/list.jade | 231 ++++++++++-------- .../coffee/app/main/user-details.coffee | 36 +-- .../public/stylesheets/app/project-list.less | 15 ++ 3 files changed, 164 insertions(+), 118 deletions(-) diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index f5d87f3038..d0bf122eed 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -26,17 +26,17 @@ block content ul.dropdown-menu(role="menu") li a( - href="#", + href, ng-click="openCreateProjectModal()" ) Blank Project li a( - href="#", + href, ng-click="openCreateProjectModal('example')" ) Example Project li a( - href="#", + href, ng-click="openUploadProjectModal()" ) Upload Project li.divider @@ -55,7 +55,8 @@ block content a.menu-indent(href="/templates/bibliographies") Bibliographies li a.menu-indent(href="/templates") View All » - .row-spaced + + .row-spaced(ng-if="projects.length > 0", ng-cloak) ul.list-unstyled.folders-menu( ng-controller="TagListController" ) @@ -85,68 +86,33 @@ block content | {{tag.name}} span.subdued ({{tag.project_ids.length}}) + .row-spaced(ng-if="projects.length == 0", ng-cloak) + .first-project + div + i.fa.fa-arrow-up.fa-2x + div + strong Create your first project! + -if (settings.enableSubscriptions) - .row-spaced + .row-spaced(ng-if="projects.length > 0", ng-cloak) a(href="/user/bonus").btn.btn-info Upgrade Account - if (showUserDetailsArea) - .row-spaced#userProfileInformation(ng-cloak) - div(ng-controller="UpdateForm").userProfileInformationArea - div(ng-hide="hidePersonalInfoSection").alert.alert-info - div(ng-show="percentComplete >= 100") - h4 100% complete, well done! - div(ng-hide="percentComplete >= 100") - h4 Your profile is - strong {{percentComplete}}% - | complete - + .row-spaced#userProfileInformation(ng-if="projects.length > 0 && percentComplete < 100", ng-cloak) + div(ng-controller="UserProfileController") + .alert.alert-info.text-centered.user-profile .progress .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") - button#completeUserProfileInformation.btn.btn-primary( - ng-hide="formVisable", - ng-click="showForm()" - ) Complete now + p.small + | Your profile is + strong {{percentComplete}}% + | complete - div(ng-show="formVisable") - form(enctype='multipart/form-data', method='post') - .form-group - input.form-control( - type='text', - name='first_name', - ng-model="userInfoForm.first_name", - ng-blur="sendUpdate()", - placeholder="First Name", - select-when="formVisable" - ) - .form-group - input.form-control( - type='text', - name='last_name', - ng-model="userInfoForm.last_name", - ng-blur="sendUpdate()", - placeholder='Last Name' - ) - .form-group#institution_auto_complete - autocomplete( - ng-model="userInfoForm.institution", - data="institutions", - ng-blur="sendUpdate()", - on-type="updateInstitutionsList", - attr-placeholder="Institution", - attr-inputclass="form-control" - ) - .form-group - input.form-control( - type='text', - name='role', - ng-model="userInfoForm.role", - placeholder='Role', - ng-blur="sendUpdate()", - list="_roles" - ) - datalist#_roles - option(ng-repeat='role in roles') {{role}} + button#completeUserProfileInformation.btn.btn-primary.btn-sm( + ng-hide="formVisable", + ng-click="openUserProfileModal()" + ) Complete .col-md-10 .container-fluid @@ -258,52 +224,59 @@ block content .row.row-spaced .col-md-12 .card.card-thin - - if (projects.length > 0) - ul.list-unstyled.project-list(select-all-list, ng-cloak) - li.container-fluid - .row - .col-md-6 - input.select-all( - select-all, - type="checkbox" - ) - span.title TITLE - .col-md-2 - span.owner OWNER - .col-md-4 - span.last-modified LAST MODIFIED - li.project_entry.container-fluid( - ng-repeat="project in visibleProjects | orderBy:'lastUpdated':true", - ng-controller="ProjectListItemController" - ) - .row - .col-md-6 - input.select-item( - select-individual, - type="checkbox", - ng-model="project.selected" - ) - span.title - a.projectName(href="/project/{{project.id}}") {{project.name}} - span - span.label.label-default(ng-repeat='tag in project.tags') - | {{tag.name}} - .col-md-2 - span.owner {{ownerName()}} - .col-md-4 - span.last-modified.isoDate {{project.lastUpdated | formatDate}} - - else - .row - .span12 - .welcome - h1 - i.fa.fa-arrow-left - | Welcome! Follow the arrow to get started - p New to LaTeX? Start by having a look at our - a(href="/templates") templates - | or - a(href="/learn") help guides - | . + ul.list-unstyled.project-list( + select-all-list, + ng-if="projects.length > 0", + ng-cloak + ) + li.container-fluid + .row + .col-md-6 + input.select-all( + select-all, + type="checkbox" + ) + span.title TITLE + .col-md-2 + span.owner OWNER + .col-md-4 + span.last-modified LAST MODIFIED + li.project_entry.container-fluid( + ng-repeat="project in visibleProjects | orderBy:'lastUpdated':true", + ng-controller="ProjectListItemController" + ) + .row + .col-md-6 + input.select-item( + select-individual, + type="checkbox", + ng-model="project.selected" + ) + span.title + a.projectName(href="/project/{{project.id}}") {{project.name}} + span + span.label.label-default(ng-repeat='tag in project.tags') + | {{tag.name}} + .col-md-2 + span.owner {{ownerName()}} + .col-md-4 + span.last-modified.isoDate {{project.lastUpdated | formatDate}} + li( + ng-if="visibleProjects.length == 0", + ng-cloak + ) + .row + .col-md-12.text-centered + small No projects + div.welcome.text-centered(ng-if="projects.length == 0", ng-cloak) + h2 Welcome to ShareLaTeX! + p New to LaTeX? Start by having a look at our + a(href="/templates") templates + | or + a(href="/learn") help guides + | , + br + | or create your first project on the left. script(type='text/ng-template', id='newTagModalTemplate') .modal-header @@ -436,3 +409,53 @@ block content span Upload a zipped project .modal-footer button.btn.btn-default(ng-click="cancel()") Cancel + + script(type="text/ng-template", id="userProfileModalTemplate") + .modal-header + h3 Your Profile + .modal-body + form(enctype='multipart/form-data', method='post') + .form-group + label(for="first_name") First Name + input.form-control( + type='text', + name='first_name', + ng-model="userInfoForm.first_name", + ng-blur="sendUpdate()", + placeholder="First Name", + select-when="formVisable" + ) + .form-group + label(for="last_name") Last Name + input.form-control( + type='text', + name='last_name', + ng-model="userInfoForm.last_name", + ng-blur="sendUpdate()", + placeholder='Last Name' + ) + .form-group#institution_auto_complete + label(for="institution") Institution + autocomplete( + ng-model="userInfoForm.institution", + name="institution", + data="institutions", + ng-blur="sendUpdate()", + on-type="updateInstitutionsList", + attr-placeholder="Institution", + attr-inputclass="form-control" + ) + .form-group + label(for="role") Role + input.form-control( + type='text', + name='role', + ng-model="userInfoForm.role", + placeholder='Role', + ng-blur="sendUpdate()", + list="_roles" + ) + datalist#_roles + option(ng-repeat='role in roles') {{role}} + .modal-footer + button.btn.btn-info(ng-click="done()") Done diff --git a/services/web/public/coffee/app/main/user-details.coffee b/services/web/public/coffee/app/main/user-details.coffee index ce6258f16d..0d8413a835 100644 --- a/services/web/public/coffee/app/main/user-details.coffee +++ b/services/web/public/coffee/app/main/user-details.coffee @@ -5,12 +5,8 @@ define [ app.factory "Institutions", -> new AlgoliaSearch(window.algolia.institutions.app_id, window.algolia.institutions.api_key).initIndex("institutions") - App.controller "UpdateForm", ($scope, $http, Institutions)-> + App.controller "UserProfileController", ($scope, $modal, $http)-> $scope.institutions = [] - $scope.formVisable = false - $scope.hidePersonalInfoSection = true - $scope.roles = ["Student", "Post-graduate student", "Post-doctoral researcher", "Lecturer", "Professor"] - $http.get("/user/personal_info").success (data)-> $scope.userInfoForm = first_name: data.first_name || "" @@ -19,23 +15,33 @@ define [ institution: data.institution || "" _csrf : window.csrfToken - if getPercentComplete() != 100 - $scope.percentComplete = getPercentComplete() - $scope.hidePersonalInfoSection = false - $scope.showForm = -> $scope.formVisable = true + $scope.getPercentComplete = -> + results = _.filter $scope.userInfoForm, (value)-> !value? or value?.length != 0 + results.length * 20 + + $scope.$watch "userInfoForm", (value) -> + if value? + $scope.percentComplete = $scope.getPercentComplete() + , true + + $scope.openUserProfileModal = () -> + $modal.open { + templateUrl: "userProfileModalTemplate" + controller: "UserProfileModalController" + scope: $scope + } + + App.controller "UserProfileModalController", ($scope, $modalInstance, $http, Institutions) -> + $scope.roles = ["Student", "Post-graduate student", "Post-doctoral researcher", "Lecturer", "Professor"] + $scope.sendUpdate = -> request = $http.post "/user/settings", $scope.userInfoForm request.success (data, status)-> request.error (data, status)-> console.log "the request failed" - $scope.percentComplete = getPercentComplete() - - getPercentComplete = -> - results = _.filter $scope.userInfoForm, (value)-> !value? or value?.length != 0 - results.length * 20 $scope.updateInstitutionsList = (inputVal)-> @@ -48,3 +54,5 @@ define [ $scope.institutions = _.map response.hits, (institution)-> "#{institution.name} (#{institution.domain})" + $scope.done = () -> + $modalInstance.close() diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index c01d8a01b8..56e4ff2f04 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -13,6 +13,21 @@ display: inline; } +.first-project { + width: 127px; + text-align: center; +} + +.user-profile { + .progress { + height: @line-height-computed / 2; + margin-bottom: @line-height-computed / 4; + } + p { + margin-bottom: @line-height-computed / 4; + } +} + ul.folders-menu { margin: 0; .subdued { From 068d88ec0b6fad486642ecc5f9efb8315877651a Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 15:54:40 +0100 Subject: [PATCH 6/9] Style bonus page --- .../Features/Project/ProjectController.coffee | 3 + services/web/app/views/project/list.jade | 14 +- services/web/app/views/referal/bonus.jade | 172 +++++++++--------- .../web/public/stylesheets/app/bonus.less | 130 +++++++++++++ services/web/public/stylesheets/style.less | 9 +- 5 files changed, 235 insertions(+), 93 deletions(-) create mode 100644 services/web/public/stylesheets/app/bonus.less diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 5c3393bdbb..f077c87145 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -123,6 +123,8 @@ module.exports = ProjectController = TagsHandler.getAllTags user_id, cb projects: (cb)-> Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb + subscription: (cb)-> + SubscriptionLocator.getUsersSubscription user_id, cb }, (err, results)-> if err? logger.err err:err, "error getting data for project list page" @@ -138,6 +140,7 @@ module.exports = ProjectController = priority_title: true projects: projects tags: tags + hasSubscription: !!results.subscription } if Settings?.algolia?.institutions?.app_id? and Settings?.algolia?.institutions?.api_key? diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index d0bf122eed..cc9b88585d 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -68,7 +68,7 @@ block content a(href, ng-click="filterProjects('shared')") Shared projects li(ng-class="{active: (filter == 'archived')}") a(href, ng-click="filterProjects('archived')") Deleted projects - li + li(ng-if="tags.length > 0") h2 Folders li( ng-repeat="tag in tags | filter:nonEmpty", @@ -93,9 +93,15 @@ block content div strong Create your first project! - -if (settings.enableSubscriptions) - .row-spaced(ng-if="projects.length > 0", ng-cloak) - a(href="/user/bonus").btn.btn-info Upgrade Account + -if (settings.enableSubscriptions && !hasSubscription) + .row-spaced(ng-if="projects.length > 0", ng-cloak).text-centered + hr + p.small You are using the free version of ShareLaTeX. + p + a(href="/user/subscription/plans").btn.btn-info Upgrade + p.small + | or unlock some free bonus features by + a(href="/user/bonus") sharing ShareLaTeX. - if (showUserDetailsArea) .row-spaced#userProfileInformation(ng-if="projects.length > 0 && percentComplete < 100", ng-cloak) diff --git a/services/web/app/views/referal/bonus.jade b/services/web/app/views/referal/bonus.jade index 9d4e742800..7a1ec48257 100644 --- a/services/web/app/views/referal/bonus.jade +++ b/services/web/app/views/referal/bonus.jade @@ -1,104 +1,106 @@ extends ../layout block content - .container.bonus.box - .row - .span8.offset2 - .page-header - h1 Recommend ShareLaTeX. Get free stuff. + .content.content-alt + .container.bonus + .row + .col-md-8.col-md-offset-2 + .card + .container-fluid + .row + .col-md-12 + .page-header + h1 Help us spread the word about ShareLaTeX. - .row - .span6.offset3 - h2 Help us spread the word about ShareLaTeX. + .row + .col-md-10.col-md-offset-1 + h2 Share ShareLaTeX with your friends and colleagues and unlock the rewards below - .row - .span4.offset4.bonus-banner - .bonus-top + .row + .col-md-8.col-md-offset-2.bonus-banner + .bonus-top - .row - .span4.offset4.bonus-banner - .title - a(href='https://twitter.com/share?text=is%20trying%20out%20the%20online%20LaTeX%20Editor%20ShareLaTeX&url=#{encodeURIComponent(buildReferalUrl("t"))}&counturl=https://www.sharelatex.com', target="_blank").twitter Tweet - - .row - .span4.offset4.bonus-banner - .title - a(href='#', onclick='postToFeed(); return false;').facebook Post on Facebook + .row + .col-md-8.col-md-offset-2.bonus-banner + .title + a(href='https://twitter.com/share?text=is%20trying%20out%20the%20online%20LaTeX%20Editor%20ShareLaTeX&url=#{encodeURIComponent(buildReferalUrl("t"))}&counturl=https://www.sharelatex.com', target="_blank").twitter Tweet + + .row + .col-md-8.col-md-offset-2.bonus-banner + .title + a(href='#', onclick='postToFeed(); return false;').facebook Post on Facebook - .row - .span4.offset4.bonus-banner - .title - a(href="https://plus.google.com/share?url=#{encodeURIComponent(buildReferalUrl("gp"))}", onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus Share us on Google+ - - .row - .span4.offset4.bonus-banner - .title - a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. #{encodeURIComponent(buildReferalUrl("e"))}', title='Share by Email').email Email us to your friends + .row + .col-md-8.col-md-offset-2.bonus-banner + .title + a(href="https://plus.google.com/share?url=#{encodeURIComponent(buildReferalUrl('gp'))}", onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus Share us on Google+ + + .row + .col-md-8.col-md-offset-2.bonus-banner + .title + a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. #{encodeURIComponent(buildReferalUrl("e"))}', title='Share by Email').email Email us to your friends - .row - .span4.offset4.bonus-banner - .title - a(href='#link-modal', data-toggle="modal").link Link to us from your website + .row + .col-md-8.col-md-offset-2.bonus-banner + .title + a(href='#link-modal', data-toggle="modal").link Link to us from your website - .row - .span4.offset4.bonus-banner - h2.direct-link Direct Link - .well #{buildReferalUrl("d")} + .row + .col-md-10.col-md-offset-1.bonus-banner + h2.direct-link Direct Link + pre.text-centered #{buildReferalUrl("d")} - .row.ab-bonus - .span6.offset3 - p.thanks When someone starts using ShareLaTeX after your recommendation we'll give you some free stuff to say thanks! Check your progress below. - .row.ab-bonus - .span6.offset3(style="position: relative; height: 30px; margin-top: 20px;") - - for (var i = 0; i <= 10; i++) { - - if (refered_user_count == i) - .number(style="left: #{i}0%").active #{i} - - else - .number(style="left: #{i}0%") #{i} - - } + .row.ab-bonus + .col-md-10.col-md-offset-1.bonus-banner + p.thanks When someone starts using ShareLaTeX after your recommendation we'll give you some free stuff to say thanks! Check your progress below. + .row.ab-bonus + .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 30px; margin-top: 20px;") + - for (var i = 0; i <= 10; i++) { + - if (refered_user_count == i) + .number(style="left: #{i}0%").active #{i} + - else + .number(style="left: #{i}0%") #{i} + - } - .row.ab-bonus - .span6.offset3 - .progress(style="height: 25px") - - if (refered_user_count == 0) - div(style="text-align: center; padding: 4px;") Spread the word and fill this bar up - .bar(style="width: #{refered_user_count}0%") + .row.ab-bonus + .col-md-10.col-md-offset-1.bonus-banner + .progress + - if (refered_user_count == 0) + div(style="text-align: center; padding: 4px;") Spread the word and fill this bar up + .progress-bar.progress-bar-info(style="width: #{refered_user_count}0%") - .row.ab-bonus - .span6.offset3(style="position: relative; height: 70px;") - .perk(style="left: 10%;", class = refered_user_count >= 1 ? "active" : "") One free collaborator - .perk(style="left: 30%;", class = refered_user_count >= 3 ? "active" : "") Three free collaborators - .perk(style="left: 60%;", class = refered_user_count >= 6 ? "active" : "") Free Dropbox and History - .perk(style="left: 90%;", class = refered_user_count >= 9 ? "active" : "") Free Professional account + .row.ab-bonus + .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 70px;") + .perk(style="left: 10%;", class = refered_user_count >= 1 ? "active" : "") One free collaborator + .perk(style="left: 30%;", class = refered_user_count >= 3 ? "active" : "") Three free collaborators + .perk(style="left: 60%;", class = refered_user_count >= 6 ? "active" : "") Free Dropbox and History + .perk(style="left: 90%;", class = refered_user_count >= 9 ? "active" : "") Free Professional account - .row.ab-bonus - .span6.offset3 - - if (refered_user_count == 0) - p.thanks You've not introduced anyone to ShareLaTeX yet. Get sharing! - - else if (refered_user_count == 1) - p.thanks You've introduced #{refered_user_count} person to ShareLaTeX. Good job, but can you get some more? - - else - p.thanks You've introduced #{refered_user_count} people to ShareLaTeX. Good job! - - #link-modal.modal.hide - .modal-header - h3 Link to ShareLaTeX - .modal-body - p You can link to ShareLaTeX with the following HTML: - p - textarea(readonly=true) - Online LaTeX Editor ShareLaTeX - p Thanks! - .modal-footer - button.btn(data-dismiss="modal") Close - - include ../general/social-footer - include ../general/small-footer + .row.ab-bonus + .col-md-10.col-md-offset-1.bonus-banner + - if (refered_user_count == 0) + p.thanks You've not introduced anyone to ShareLaTeX yet. Get sharing! + - else if (refered_user_count == 1) + p.thanks You've introduced #{refered_user_count} person to ShareLaTeX. Good job, but can you get some more? + - else + p.thanks You've introduced #{refered_user_count} people to ShareLaTeX. Good job! + + #link-modal.modal.hide + .modal-header + h3 Link to ShareLaTeX + .modal-body + p You can link to ShareLaTeX with the following HTML: + p + textarea(readonly=true) + Online LaTeX Editor ShareLaTeX + p Thanks! + .modal-footer + button.btn(data-dismiss="modal") Close script(type='text/javascript', src='//platform.twitter.com/widgets.js') script(src='https://connect.facebook.net/en_US/all.js') - script(type='text/javascript') + script(type='text/javascript'). FB.init({appId: "148710621956179", status: true, cookie: true}); function postToFeed() { @@ -121,7 +123,7 @@ block content FB.ui(obj, callback); } - script(type="text/javascript") + script(type="text/javascript"). $(function() { $(".twitter").click(function() { ga('send', 'event', 'referal-button', 'clicked', "twitter") diff --git a/services/web/public/stylesheets/app/bonus.less b/services/web/public/stylesheets/app/bonus.less new file mode 100644 index 0000000000..9c5f39acc1 --- /dev/null +++ b/services/web/public/stylesheets/app/bonus.less @@ -0,0 +1,130 @@ +.bonus { + margin-top: 15px; + .page-header h1 { + text-align: center; + } + + h2 { + text-align: center; + font-size: 20px; + line-height: 28px; + margin-bottom: @line-height-computed; + margin-top: 0; + &.direct-link { + margin-top: @line-height-computed; + } + } + + .bonus-banner { + .bonus-top { + border-bottom: 1px solid lighten(@blue, 40%); + } + .title { + a { + display: block; + font-size: 18px; + padding: 20px 62px; + background-color: white; + border-bottom: 1px solid lighten(@blue, 40%); + color: @blue; + &:hover { + background-color: lighten(@blue, 45%); + } + } + } + a.twitter { + background-image: url(/img/social/twitter-32.png); + background-repeat: no-repeat; + background-position: 16px center; + } + a.facebook { + background-image: url(/img/social/facebook-32.png); + background-repeat: no-repeat; + background-position: 16px center; + } + a.email { + background-image: url(/img/social/mail-32.png); + background-repeat: no-repeat; + background-position: 16px center; + } + a.google-plus { + background-image: url(//www.gstatic.com/images/icons/gplus-32.png); + background-repeat: no-repeat; + background-position: 16px center; + } + a.link { + background-image: url(/img/social/link-32.png); + background-repeat: no-repeat; + background-position: 16px center; + } + h2.direct-link { + } + } + + p.thanks { + font-size: 18px; + line-height: 28px; + margin-top: 10px; + text-align: center; + } + + .number { + position: absolute; + margin-left: -13px; + width: 26px; + padding: 3px 0; + text-align: center; + background-color: #ddd; + &.active { + background-color: @blue; + color: white; + } + border-radius: 3px; + } + + .progress { + margin-top: @line-height-computed / 2; + margin-left: -15px; + margin-right: -15px; + height: 30px; + } + + .perk { + position: absolute; + background-color: #ddd; + border-radius: 5px; + text-align: center; + padding: 5px 5px; + width: 100px; + margin-left: -50px; + font-size: 14px; + &:before { + border-bottom: 8px solid #ddd; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + content: ''; + position: absolute; + left: 42px; + top: -8px; + } + &.active { + color: white; + background-color: @blue; + &:before { + border-bottom: 8px solid @blue; + } + } + } + +} + +#link-modal { + .modal-body{ + text-align: center; + } + textarea { + width: 95%; + margin-bottom: 0; + } +} + diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less index 626ac3e2fa..0da8677a6e 100755 --- a/services/web/public/stylesheets/style.less +++ b/services/web/public/stylesheets/style.less @@ -34,10 +34,10 @@ //@import "components/thumbnails.less"; @import "components/alerts.less"; @import "components/progress-bars.less"; -@import "components/media.less"; -@import "components/list-group.less"; -@import "components/panels.less"; -@import "components/wells.less"; +// @import "components/media.less"; +// @import "components/list-group.less"; +// @import "components/panels.less"; +// @import "components/wells.less"; @import "components/close.less"; @import "components/fineupload.less"; @@ -60,3 +60,4 @@ @import "app/homepage.less"; @import "app/plans.less"; @import "app/recurly.less"; +@import "app/bonus.less"; From 5d62775ab6aa9b351bc11c090b9de1fe98eb3fe7 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 16:22:38 +0100 Subject: [PATCH 7/9] Fix auto complete in user profile --- services/web/app/views/project/list.jade | 31 +++++---- .../public/stylesheets/app/project-list.less | 65 +++++++++++++++++-- 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index cc9b88585d..c21785a8e0 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -93,20 +93,11 @@ block content div strong Create your first project! - -if (settings.enableSubscriptions && !hasSubscription) - .row-spaced(ng-if="projects.length > 0", ng-cloak).text-centered - hr - p.small You are using the free version of ShareLaTeX. - p - a(href="/user/subscription/plans").btn.btn-info Upgrade - p.small - | or unlock some free bonus features by - a(href="/user/bonus") sharing ShareLaTeX. - - if (showUserDetailsArea) - .row-spaced#userProfileInformation(ng-if="projects.length > 0 && percentComplete < 100", ng-cloak) + .row-spaced#userProfileInformation(ng-if="projects.length > 0", ng-cloak) div(ng-controller="UserProfileController") - .alert.alert-info.text-centered.user-profile + hr(ng-show="percentComplete < 100") + .text-centered.user-profile(ng-show="percentComplete < 100") .progress .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") @@ -115,11 +106,21 @@ block content strong {{percentComplete}}% | complete - button#completeUserProfileInformation.btn.btn-primary.btn-sm( + button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", ng-click="openUserProfileModal()" ) Complete + -if (settings.enableSubscriptions && !hasSubscription) + .row-spaced(ng-if="projects.length > 0", ng-cloak).text-centered + hr + p.small You are using the free version of ShareLaTeX. + p + a(href="/user/subscription/plans").btn.btn-primary Upgrade + p.small + | or unlock some free bonus features by + a(href="/user/bonus") sharing ShareLaTeX. + .col-md-10 .container-fluid .row @@ -459,9 +460,7 @@ block content ng-model="userInfoForm.role", placeholder='Role', ng-blur="sendUpdate()", - list="_roles" + typeahead="role for role in roles" ) - datalist#_roles - option(ng-repeat='role in roles') {{role}} .modal-footer button.btn.btn-info(ng-click="done()") Done diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 56e4ff2f04..626b4ac76a 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -1,16 +1,16 @@ .project-header { - .btn-group > .btn { - padding-left: @line-height-base / 2; - padding-right: @line-height-base / 2; - } + .btn-group > .btn { + padding-left: @line-height-base / 2; + padding-right: @line-height-base / 2; + } } .project-search { - margin: @line-height-base 0; + margin: @line-height-base 0; } .project-tools { - display: inline; + display: inline; } .first-project { @@ -40,7 +40,7 @@ ul.folders-menu { color: #333; padding: (@line-height-computed / 4); } - } + } li.active { //border-right: 4px solid @red; a { @@ -124,3 +124,54 @@ ul.project-list { } } } + +#institution_auto_complete { + + ul>li{ + list-style:none; + } + + .autocomplete { + width: 100%; + position: relative; + } + + .autocomplete ul { + position: absolute; + top: 100%; + left: 0; + z-index: @zindex-dropdown; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: @font-size-base; + background-color: @dropdown-bg; + border: 1px solid @dropdown-fallback-border; // IE8 fallback + border: 1px solid @dropdown-border; + border-radius: @border-radius-base; + .box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Links within the dropdown menu + > li { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @line-height-base; + color: @dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } + + > li.active { + text-decoration: none; + color: @dropdown-link-hover-color; + background-color: @dropdown-link-hover-bg; + } + } + .autocomplete .highlight { + font-weight: 700; + } +} From c542116b413706c5427b5aa367f359646a27ecd3 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 16:25:18 +0100 Subject: [PATCH 8/9] Fix role autocomplete --- services/web/app/views/project/list.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index c21785a8e0..cfaacb498d 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -460,7 +460,7 @@ block content ng-model="userInfoForm.role", placeholder='Role', ng-blur="sendUpdate()", - typeahead="role for role in roles" + typeahead="role for role in roles | filter:$viewValue" ) .modal-footer button.btn.btn-info(ng-click="done()") Done From 8525bf4a7215e07ccd05bd01e88879b5ddbfabb0 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 7 Jul 2014 18:06:12 +0100 Subject: [PATCH 9/9] Get group admin page working with angular and new style --- services/web/app/views/project/list.jade | 8 +- .../app/views/subscriptions/group_admin.jade | 118 +++++++++++------- .../coffee/app/directives/selectAll.coffee | 67 ++++++++++ services/web/public/coffee/app/main.coffee | 2 + .../coffee/app/main/group-members.coffee | 54 ++++++++ .../coffee/app/main/project-list.coffee | 65 ---------- .../public/stylesheets/app/project-list.less | 11 +- 7 files changed, 210 insertions(+), 115 deletions(-) create mode 100644 services/web/public/coffee/app/directives/selectAll.coffee create mode 100644 services/web/public/coffee/app/main/group-members.coffee diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index cfaacb498d..e75b5091fb 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -231,7 +231,7 @@ block content .row.row-spaced .col-md-12 .card.card-thin - ul.list-unstyled.project-list( + ul.list-unstyled.project-list.structured-list( select-all-list, ng-if="projects.length > 0", ng-cloak @@ -243,11 +243,11 @@ block content select-all, type="checkbox" ) - span.title TITLE + | TITLE .col-md-2 - span.owner OWNER + | OWNER .col-md-4 - span.last-modified LAST MODIFIED + | LAST MODIFIED li.project_entry.container-fluid( ng-repeat="project in visibleProjects | orderBy:'lastUpdated':true", ng-controller="ProjectListItemController" diff --git a/services/web/app/views/subscriptions/group_admin.jade b/services/web/app/views/subscriptions/group_admin.jade index 823b57beeb..fba2978e83 100644 --- a/services/web/app/views/subscriptions/group_admin.jade +++ b/services/web/app/views/subscriptions/group_admin.jade @@ -1,52 +1,84 @@ extends ../layout block content - .container.box - .row - .span12 - .page-header - h2 Group Admin + .content.content-alt + .container + .row + .col-md-10.col-md-offset-1 + .card(ng-controller="GroupMembersController") + .page-header + .pull-right(ng-cloak) + small(ng-show="selectedUsers.length == 0") You have added {{ users.length }} of {{ groupSize }} available members + a.btn.btn-danger( + href, + ng-show="selectedUsers.length > 0" + ng-click="removeMembers()" + ) Remove from group + h1 Group Account - div You are allowed up to - strong #{subscription.membersLimit} - | members in this group + .row-spaced-small + ul.list-unstyled.structured-list( + select-all-list, + ng-cloak + ) + li.container-fluid + .row + .col-md-5 + input.select-all( + select-all, + type="checkbox" + ) + span.email EMAIL + .col-md-5 + span.name NAME + .col-md-2 + span.registered REGISTERED + li.container-fluid( + ng-repeat="user in users | orderBy:'email':true", + ng-controller="GroupMemberListItemController" + ) + .row + .col-md-5 + input.select-item( + select-individual, + type="checkbox", + ng-model="user.selected" + ) + span.email {{ user.email }} + .col-md-5 + span.name {{ user.first_name }} {{ user.last_name }} + .col-md-2 + span.registered + i.fa.fa-check.text-success(ng-show="!user.holdingAccount") + i.fa.fa-times(ng-show="user.holdingAccount") + li( + ng-if="users.length == 0", + ng-cloak + ) + .row + .col-md-12.text-centered + small No members - table.table-striped.table.table-striped - thead - tr - th - input(type="checkbox").select-all - th email - th Name - th Registered + div(ng-if="users.length < groupSize", ng-cloak) + hr + p + .small Add more members + form.form + .row + .col-xs-6 + input.form-control( + name="email", + type="text", + placeholder="jane@example.com, joe@example.com", + ng-model="inputs.emails", + on-enter="addMembers()" + ) + .col-xs-6 + button.btn.btn-primary(ng-click="addMembers()") Add - tbody#userList - -each user in users - tr - td - input(type="checkbox").select-one - td #{user.email} - td #{user.first_name} #{user.last_name} - td #{!user.holdingAccount} - td - input(type="hidden", name="user_id", value=user._id).user_id {{user._id}} - - - div - button.btn.btn-danger#deleteUsers Delete Selected - div   - div - form.well.form-inline#addUserToGroup - div - input(name="_csrf", type="hidden", value=csrfToken) - input(name="email", type="email", placeholder="someone@email.com")#newEmail.email.input-large   - button.btn.btn-primary.addUser Add - div   - div Add multiple emails seperated with commas or space. - - - - locals.supressDefaultJs = true - script(data-main='/js/SubscriptionGroupsManager.js', src='/js/libs/require.js') + script(type="text/javascript"). + window.users = !{JSON.stringify(users)}; + window.groupSize = #{subscription.membersLimit}; diff --git a/services/web/public/coffee/app/directives/selectAll.coffee b/services/web/public/coffee/app/directives/selectAll.coffee new file mode 100644 index 0000000000..79b683c20a --- /dev/null +++ b/services/web/public/coffee/app/directives/selectAll.coffee @@ -0,0 +1,67 @@ +define [ + "base" +], (App) -> + App.directive "selectAllList", () -> + return { + controller: ["$scope", ($scope) -> + # Selecting or deselecting all should apply to all projects + selectAll = () -> + $scope.$broadcast "select-all:select" + + deselectAll = () -> + $scope.$broadcast "select-all:deselect" + + clearSelectAllState = () -> + $scope.$broadcast "select-all:clear" + + return { + clearSelectAllState: clearSelectAllState + selectAll: selectAll + deselectAll: deselectAll + } + ] + link: (scope, element, attrs) -> + + + } + + App.directive "selectAll", () -> + return { + require: "^selectAllList" + link: (scope, element, attrs, selectAllListController) -> + scope.$on "select-all:clear", () -> + element.prop("checked", false) + + element.change () -> + if element.is(":checked") + selectAllListController.selectAll() + else + selectAllListController.deselectAll() + return true + } + + App.directive "selectIndividual", () -> + return { + require: "^selectAllList" + scope: { + ngModel: "=" + } + link: (scope, element, attrs, selectAllListController) -> + ignoreChanges = false + + scope.$watch "ngModel", (value) -> + if value? and !ignoreChanges + selectAllListController.clearSelectAllState() + + scope.$on "select-all:select", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = true + ignoreChanges = false + + scope.$on "select-all:deselect", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = false + ignoreChanges = false + } \ No newline at end of file diff --git a/services/web/public/coffee/app/main.coffee b/services/web/public/coffee/app/main.coffee index 452c661b8f..c2a70fdb78 100644 --- a/services/web/public/coffee/app/main.coffee +++ b/services/web/public/coffee/app/main.coffee @@ -3,12 +3,14 @@ define [ "main/user-details" "main/account-settings" "main/plans" + "main/group-members" "directives/asyncForm" "directives/stopPropagation" "directives/focus" "directives/equals" "directives/fineUpload" "directives/onEnter" + "directives/selectAll" "filters/formatDate" ], () -> angular.bootstrap(document.body, ["SharelatexApp"]) \ No newline at end of file diff --git a/services/web/public/coffee/app/main/group-members.coffee b/services/web/public/coffee/app/main/group-members.coffee new file mode 100644 index 0000000000..f1f61623ee --- /dev/null +++ b/services/web/public/coffee/app/main/group-members.coffee @@ -0,0 +1,54 @@ +define [ + "base" +], (App) -> + App.controller "GroupMembersController", ($scope, queuedHttp) -> + $scope.users = window.users + $scope.groupSize = window.groupSize + $scope.selectedUsers = [] + + $scope.inputs = + emails: "" + + parseEmails = (emailsString)-> + regexBySpaceOrComma = /[\s,]+/ + emails = emailsString.split(regexBySpaceOrComma) + emails = _.map emails, (email)-> + email = email.trim() + emails = _.select emails, (email)-> + email.indexOf("@") != -1 + return emails + + $scope.addMembers = () -> + emails = parseEmails($scope.inputs.emails) + for email in emails + queuedHttp + .post("/subscription/group/user", { + email: email, + _csrf: window.csrfToken + }) + .success (data) -> + $scope.users.push data.user if data.user? + $scope.inputs.emails = "" + + $scope.removeMembers = () -> + for user in $scope.selectedUsers + do (user) -> + queuedHttp({ + method: "DELETE", + url: "/subscription/group/user/#{user._id}" + headers: + "X-Csrf-Token": window.csrfToken + }) + .success () -> + index = $scope.users.indexOf(user) + return if index == -1 + $scope.users.splice(index, 1) + $scope.selectedUsers = [] + + $scope.updateSelectedUsers = () -> + $scope.selectedUsers = $scope.users.filter (user) -> user.selected + + App.controller "GroupMemberListItemController", ($scope) -> + $scope.$watch "user.selected", (value) -> + if value? + $scope.updateSelectedUsers() \ No newline at end of file diff --git a/services/web/public/coffee/app/main/project-list.coffee b/services/web/public/coffee/app/main/project-list.coffee index dc4a76c75c..0ad0c94660 100644 --- a/services/web/public/coffee/app/main/project-list.coffee +++ b/services/web/public/coffee/app/main/project-list.coffee @@ -1,71 +1,6 @@ define [ "base" ], (App) -> - App.directive "selectAllList", () -> - return { - controller: ["$scope", ($scope) -> - # Selecting or deselecting all should apply to all projects - selectAll = () -> - $scope.$broadcast "select-all:select" - - deselectAll = () -> - $scope.$broadcast "select-all:deselect" - - clearSelectAllState = () -> - $scope.$broadcast "select-all:clear" - - return { - clearSelectAllState: clearSelectAllState - selectAll: selectAll - deselectAll: deselectAll - } - ] - link: (scope, element, attrs) -> - - - } - - App.directive "selectAll", () -> - return { - require: "^selectAllList" - link: (scope, element, attrs, selectAllListController) -> - scope.$on "select-all:clear", () -> - element.prop("checked", false) - - element.change () -> - if element.is(":checked") - selectAllListController.selectAll() - else - selectAllListController.deselectAll() - return true - } - - App.directive "selectIndividual", () -> - return { - require: "^selectAllList" - scope: { - ngModel: "=" - } - link: (scope, element, attrs, selectAllListController) -> - ignoreChanges = false - - scope.$watch "ngModel", (value) -> - if value? and !ignoreChanges - selectAllListController.clearSelectAllState() - - scope.$on "select-all:select", () -> - ignoreChanges = true - scope.$apply () -> - scope.ngModel = true - ignoreChanges = false - - scope.$on "select-all:deselect", () -> - ignoreChanges = true - scope.$apply () -> - scope.ngModel = false - ignoreChanges = false - } - App.factory "queuedHttp", ["$http", "$q", ($http, $q) -> pendingRequests = [] inflight = false diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 626b4ac76a..6e1ebe3aed 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -68,16 +68,16 @@ form.project-search { } } -ul.project-list { +ul.structured-list { list-style-type: none; margin: 0; overflow: hidden; - overflow-y: scroll; + overflow-y: auto; li { border-bottom: 1px solid @gray-lightest; padding: (@line-height-computed / 4) 0; &:first-child { - .last-modified, .owner { + .header { font-size: 1rem; } } @@ -101,6 +101,11 @@ ul.project-list { .select-item, .select-all { margin-left: @line-height-computed / 4; } + } +} + +ul.project-list { + li { .last-modified, .owner { font-size: .8rem; }