diff --git a/401.spt b/401.spt index 8aa8fe5047..addc6c8246 100644 --- a/401.spt +++ b/401.spt @@ -10,6 +10,15 @@ suppress_sidebar = True {{ sign_in_using(button_class='large') }} {% endblock %} + +{% block scripts %} + +{{ super() }} +{% endblock %} [---] application/json via stdlib_percent { "error_code": 401 , "error_message_short": "Unauthenticated" diff --git a/js/gratipay.js b/js/gratipay.js index 68eb6483df..38f0ccb3a8 100644 --- a/js/gratipay.js +++ b/js/gratipay.js @@ -14,7 +14,7 @@ Gratipay.init = function() { Gratipay.warnOffUsersFromDeveloperConsole(); Gratipay.adaptToLongUsernames(); Gratipay.forms.initCSRF(); - Gratipay.signIn(); + Gratipay.signIn.wireUpButton(); Gratipay.signOut(); Gratipay.payments.initSupportGratipay(); Gratipay.tabs.init(); @@ -108,34 +108,6 @@ Gratipay.jsonml = function(jsonml) { return node; }; -Gratipay.signIn = function() { - $('.sign-in > .dropdown').mouseenter(function(e) { - clearTimeout($(this).data('timeoutId')); - $(this).addClass('open'); - }).mouseleave(function(e) { - var $this = $(this), - timeoutId = setTimeout(function() { - $this.removeClass('open'); - }, 100); - $this.data('timeoutId', timeoutId); - }); - - $('.dropdown-toggle').click(function(e) { - if ($('.sign-in > .dropdown').hasClass('open')) { - e.preventDefault(); - return false; - } - else { - $(this).addClass('open'); - } - }); - - // disable the tip-changed prompt when trying to sign in - $('form.auth-button').submit(function() { - $(window).off('beforeunload.tips'); - }); -}; - Gratipay.signOut = function() { $('a#sign-out').click(function(e) { e.preventDefault(); diff --git a/js/gratipay/sign-in.js b/js/gratipay/sign-in.js new file mode 100644 index 0000000000..a068b8b9d3 --- /dev/null +++ b/js/gratipay/sign-in.js @@ -0,0 +1,22 @@ +Gratipay.signIn = {}; + +Gratipay.signIn.wireUpButton = function() { + $('.sign-in button').click(Gratipay.signIn.openSignInOrSignUpModal); +} + +Gratipay.signIn.openSignInToContinueModal = function () { + Gratipay.signIn.replaceTextInModal('sign-in-to-continue'); + Gratipay.modal.open('#sign-in-modal'); +} + +Gratipay.signIn.openSignInOrSignUpModal = function () { + Gratipay.signIn.replaceTextInModal('sign-in-or-sign-up'); + Gratipay.modal.open('#sign-in-modal'); +} + +Gratipay.signIn.replaceTextInModal = function(dataKey) { + $('#sign-in-modal').find('.sign-in-togglable').each(function () { + var textToReplace = $(this).data(dataKey); + $(this).text(textToReplace); + }); +} diff --git a/scss/components/modal.scss b/scss/components/modal.scss index 7a0eeb83c9..8141681cb9 100644 --- a/scss/components/modal.scss +++ b/scss/components/modal.scss @@ -45,7 +45,8 @@ left: 0; text-align: center; display: none; - background: transparentize($black, 0.3); + background: transparentize($light-brown, 0.3); + z-index: 1000; } .modal { @@ -55,8 +56,6 @@ display: inline-block; position: relative; @include border-radius($border-radius); - @include box-shadow(0, 0, 50px, $black); - margin-top: 15vh; header { @@ -64,10 +63,12 @@ width: 100%; height: $header-height; border: 1px solid $darker-black; - background: white; + background: $darker-black; + @include border-radius($border-radius $border-radius 0 0); h2 { + color: $almost-white; margin: 0; font-size: 20px; padding: 0px 20px 0px 20px; @@ -80,7 +81,7 @@ border-top: 1px solid $darker-black; border-right: 1px solid $darker-black; border-bottom: 1px solid $darker-black; - color: $darker-black; + color: $almost-white; vertical-align: middle; position: absolute; top: 0px; @@ -91,7 +92,10 @@ @include border-radius(0 $border-radius 0 0); &:hover { - background-color: $light-brown; + background-color: $gray; + border-top: 1px solid $gray; + border-right: 1px solid $gray; + border-bottom: 1px solid $gray; } } } @@ -103,8 +107,7 @@ text-align: left; overflow: auto; max-height: 70vh; - z-index: 0; - background: $lightest-gray; + background: white; @include border-radius(0 0 $border-radius $border-radius); } } diff --git a/scss/components/sign_in.scss b/scss/components/sign_in.scss index 4488ec5305..ff16a87fed 100644 --- a/scss/components/sign_in.scss +++ b/scss/components/sign_in.scss @@ -1,7 +1,8 @@ .sign-in { display: inline-block; vertical-align: middle; - .dropdown-toggle { + + button { background: $green; @include border-radius(3px); color: #fff; @@ -9,73 +10,16 @@ display: block; font-size: 13px; line-height: 25px; - .caret { - border-top: 4px solid #fff; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - margin-top: 11px; - } - &.highlight { - @include box-shadow(0, 0, 8px, $green); + + &:hover { background: $darker-green; } } - .open .dropdown-toggle, .dropdown-toggle:hover { - background: $darker-green; - } - .dropdown-menu { - top: 24px; - @include border-radius(3px); - border: 0; - box-shadow: none; - background: $green; - min-width: 140px; /* centering on unclaimed package page is based on this */ - li { - margin: 0; - list-style: none; - &.facebook button { @include has-icon("facebook"); } - &.google button { @include has-icon("google"); } - &.twitter button { @include has-icon("twitter"); } - &.github button { @include has-icon("github"); } - &.bitbucket button { @include has-icon("bitbucket"); } - &.openstreetmap button { @include has-icon("openstreetmap"); } - &:hover button { - background: $darker-green; - } - } - button { - color: #fff; - display: block; - width: 100%; - padding: 3px 8px; - margin: 0; - text-align: left; - position: relative; - font-size: 13px; - border: none; - @include border-radius(0); - background: none; - } - button:before { - position: absolute; - top: 4px; - right: 5px; - } - form { - display: block; - margin: 0; - padding: 0; - } - } + #header & { float: right; - &:hover { - .dropdown-toggle { - background: transparentize($gold, 0.8); - border-color: $gold; - } - } - .dropdown-toggle { + + button { @include border-radius(0); border-top: 4px solid transparent; background: none; @@ -92,19 +36,78 @@ margin-top: 4px; } } - .dropdown-menu { - left: auto; - top: auto; - right: 0; - margin-top: 0; - @include border-radius(0); - background: $gold; + + &:hover { button { - color: $black; - &:hover { - background: transparentize($brown, 0.8); - } + background: transparentize($gold, 0.8); + border-color: $gold; } } } } + +#sign-in-modal .modal { + max-width: 400px; + width: 90%; + section { + padding: 20px; + text-align: center; + + p { + font-size: 14px; + } + } + + .auth-links { + margin-top: 5px; + } + + .auth-links li { + display: inline-block; + margin: 5px 0px 0px 0px; + + & > * { + display: inline; + } + } + + .auth-links button { + text-align: left; + background: $green; + color: white; + padding: 15px 45px 15px 15px; + position: relative; + font: normal 14px $Ideal; + + span { + vertical-align: middle; + } + + span.icon { + position: absolute; + right: 9px; + font-size: 16px; + } + + span.icon.facebook { @include has-icon("facebook"); } + span.icon.twitter { @include has-icon("twitter"); } + span.icon.google { @include has-icon("google"); } + span.icon.github { @include has-icon("github"); } + span.icon.openstreetmap { @include has-icon("openstreetmap"); } + span.icon.bitbucket { @include has-icon("bitbucket"); } + + &:hover { + background: $darker-green; + } + } +} + +@media (max-width: 600px) { + #sign-in-modal .modal .auth-links li { + display: block; + + button { + width: 100%; + } + } +} diff --git a/scss/mixins/icons.scss b/scss/mixins/icons.scss index 2c05a4dee7..3780af618c 100644 --- a/scss/mixins/icons.scss +++ b/scss/mixins/icons.scss @@ -9,13 +9,11 @@ font-style: normal; } -@mixin icon($name: "", $font-size: 16px, $margin-right: 4px) { +@mixin icon($name: "") { font-family: 'icomoon'; speak: none; font-weight: normal; font-variant: normal; - font-size: $font-size; - margin-right: $margin-right; text-transform: none; -webkit-font-smoothing: antialiased; @@ -27,8 +25,8 @@ @else if $name == "bitbucket" { content: "\e005"; } } -@mixin has-icon($name: "", $font-size: 16px, $margin-right: 4px) { +@mixin has-icon($name: "") { &:before { - @include icon($name, $font-size, $margin-right); + @include icon($name); } } diff --git a/scss/variables.scss b/scss/variables.scss index c8e3ea86bd..49b8b7a511 100644 --- a/scss/variables.scss +++ b/scss/variables.scss @@ -25,5 +25,7 @@ $gold: #FFC300; $white: #FFF; $blue: #0073cf; +$almost-white: #FCFCFC; + // search result item width $people-column-width: 90px; diff --git a/templates/base.html b/templates/base.html index e2161da0e4..e8fa0d0c88 100644 --- a/templates/base.html +++ b/templates/base.html @@ -29,6 +29,7 @@ {% endif %} + {% include "templates/sign-in-modal.html" %}
diff --git a/templates/sign-in-modal.html b/templates/sign-in-modal.html new file mode 100644 index 0000000000..065254129c --- /dev/null +++ b/templates/sign-in-modal.html @@ -0,0 +1,46 @@ +{% from 'templates/auth.html' import auth_button with context %} + +{# + # This modal has content that can be toggled based on context. + # + # - The two contexts currently available are 'sign-in-to-continue' and + # 'sign-in-or-signup'. + # - Every element with togglable text is marked with .sign-in-togglable. + # - The versions of text are present in the data-sign-in-to-continue and + # data-sign-in-or-signup attributes for each togglable element. + # - We also place default text so that JS failures don't render the modal + # completely useless. + # + #} + + diff --git a/templates/sign-in-using.html b/templates/sign-in-using.html index be0e3fbacd..5d605f7dd5 100644 --- a/templates/sign-in-using.html +++ b/templates/sign-in-using.html @@ -1,20 +1,7 @@ -{% from 'templates/auth.html' import auth_button with context %} - {% macro sign_in_using(button_class='') %} {% endmacro %} diff --git a/tests/ttw/test_package_claiming.py b/tests/ttw/test_package_claiming.py index 0eebc9c855..b852996589 100644 --- a/tests/ttw/test_package_claiming.py +++ b/tests/ttw/test_package_claiming.py @@ -13,9 +13,9 @@ def check(self, choice=0): self.make_participant('alice', claimed_time='now') self.sign_in('alice') self.visit('/on/npm/foo/') - self.css('label')[0].click() # activate select - self.css('label')[choice].click() - self.css('button')[0].click() + self.css('#content label')[0].click() # activate select + self.css('#content label')[choice].click() + self.css('#content button')[0].click() address = ('alice' if choice == 0 else 'bob') + '@example.com' assert self.wait_for_success() == 'Check {} for a verification link.'.format(address) return self.db.one('select address from claims c join emails e on c.nonce = e.nonce') @@ -44,11 +44,11 @@ def test_disabled_items_are_disabled(self): self.add_and_verify_email(alice, 'alice@example.com', 'bob@example.com') self.sign_in('alice') self.visit('/on/npm/foo/') - self.css('label')[0].click() # activate select - self.css('label')[1].click() # click second item - self.css('li')[0].has_class('selected') # first item is still selected - self.css('ul')[0].has_class('open') # still open - self.css('button').has_class('disabled') + self.css('#content label')[0].click() # activate select + self.css('#content label')[1].click() # click second item + self.css('#content li')[0].has_class('selected') # first item is still selected + self.css('#content ul')[0].has_class('open') # still open + self.css('#content button').has_class('disabled') assert self.db.all('select * from claims') == [] diff --git a/tests/ttw/test_sign_in.py b/tests/ttw/test_sign_in.py new file mode 100644 index 0000000000..29a60ae627 --- /dev/null +++ b/tests/ttw/test_sign_in.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +from gratipay.testing import BrowserHarness + + +class Tests(BrowserHarness): + + def test_sign_in_modal_is_hidden_by_default(self): + self.visit('/') + + assert not self.css('#sign-in-modal').visible + + def test_clicking_sign_in_button_opens_up_modal(self): + self.visit('/') + + self.css('.sign-in').click() + assert self.css('#sign-in-modal').visible + + def test_clicking_close_closes_modal(self): + self.visit('/') + + self.css('.sign-in').click() + self.css('#sign-in-modal .close-modal').click() + + assert not self.css('#sign-in-modal').visible + + def test_401_page_opens_modal_automatically(self): + self.visit('/about/me/emails.json') + + assert self.css('#sign-in-modal').visible + assert self.css('#sign-in-modal p')[0].text == 'Please sign in to continue'