Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Wire it up!
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Jun 7, 2017
1 parent bc162ed commit ebf2c95
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 40 deletions.
10 changes: 5 additions & 5 deletions gratipay/testing/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ def wait_for_notification(self, type='notice'):
"""Wait for a certain ``type`` of notification. Dismiss the
notification and return the message.
"""
n_selector = '.notifications-fixed .notification-{}'.format(type)
m_selector = 'span.btn-close'
notification = self.wait_for(n_selector).first
notification_selector = '.notifications-fixed .notification-{}'.format(type)
close_button_selector = 'span.btn-close'
notification = self.wait_for(notification_selector).first
message = notification.find_by_css('div').html
notification.find_by_css(m_selector).first.click()
self.wait_to_disappear(n_selector + ' ' + m_selector)
notification.find_by_css(close_button_selector).first.click()
self.wait_to_disappear('#{} {}'.format(notification['id'], close_button_selector))
return message

def wait_for_success(self):
Expand Down
8 changes: 5 additions & 3 deletions js/gratipay/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
Gratipay.notification = function(text, type, timeout, closeCallback) {
var type = type || 'notice';
var timeout = timeout || (type == 'error' ? 10000 : 5000);

var dialog = ['div', { 'class': 'notification notification-' + type }, [ 'div', text ]];
var id = Math.random().toString(36).substring(2, 100);
var placeholder = ['div', {'class': 'notification notification-' + type}, ['div', text]];
var dialog = ['div', {'class': 'notification notification-' + type, 'id': 'notification-'+id},
['div', text]];
var $dialog = $([
Gratipay.jsonml(dialog),
Gratipay.jsonml(placeholder),
Gratipay.jsonml(dialog)
]);

Expand Down
52 changes: 41 additions & 11 deletions js/gratipay/packages.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,63 @@
Gratipay.packages = {};

Gratipay.packages.init = function() {
Gratipay.packages.initBulk = function() {
$('button.apply').on('click', Gratipay.packages.postBulk);
};

Gratipay.packages.initSingle = function() {
Gratipay.Select('.gratipay-select');
$('button.apply').on('click', Gratipay.packages.post);
$('button.apply').on('click', Gratipay.packages.postOne);
};

Gratipay.packages.post = function(e) {

Gratipay.packages.postBulk = function(e) {
e.preventDefault();
var pkg, email, package_id, emails=[], package_ids_by_email={};
$('table.listing td.item ').not('.disabled').each(function() {
pkg = $(this).data();
if (package_ids_by_email[pkg.email] === undefined) {
emails.push(pkg.email);
package_ids_by_email[pkg.email] = [];
}
package_ids_by_email[pkg.email].push(pkg.packageId);
});
emails.sort();
for (var i=0, email; email = emails[i]; i++)
Gratipay.packages.post(email, package_ids_by_email[email], true);
};

Gratipay.packages.postOne = function(e) {
e.preventDefault();
var $this = $(this);
var action = 'start-verification';
var package_id = $('input[name=package_id]').val();
var email = $('input[name=email]:checked').val();
var package_id = $('input[name=package_id]').val();
Gratipay.packages.post(email, [package_id]);
}


var $inputs = $('input, button');
$inputs.prop('disabled', true);
Gratipay.packages.post = function(email, package_ids, show_email) {
var action = 'start-verification';
var $button = $('.important-button button')

$button.prop('disabled', true);
function reenable() { $button.prop('disabled', false); }
$.ajax({
url: '/~' + Gratipay.username + '/emails/modify.json',
type: 'POST',
data: {action: action, address: email, package_id: package_id},
data: { action: action
, address: email
, package_id: package_ids
, show_address_in_message: true
},
traditional: true,
dataType: 'json',
success: function (msg) {
if (msg) {
Gratipay.notification(msg, 'success');
reenable();
}
$inputs.prop('disabled', false);
},
error: [
function () { $inputs.prop('disabled', false); },
reenable,
Gratipay.error
],
});
Expand Down
61 changes: 45 additions & 16 deletions tests/ttw/test_package_claiming.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def check(self, choice=0):
self.css('label')[0].click() # activate select
self.css('label')[choice].click()
self.css('button')[0].click()
assert self.wait_for_success() == 'Check your inbox for a verification link.'
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')

def finish_claiming(self):
Expand Down Expand Up @@ -158,23 +159,23 @@ def setUp(self):
, emails=['[email protected]', '[email protected]', '[email protected]']
)

def visit_as(self, username):
self.visit('/')
self.sign_in(username)
self.visit('/on/npm/')

def test_anon_gets_sign_in_prompt(self):
self.visit('/on/npm/')
assert self.css('.important-button button').text == 'Sign in / Sign up'

def test_auth_without_email_gets_highlighted_link_to_email(self):
self.make_participant('alice', claimed_time='now')
self.visit('/')
self.sign_in('alice')
self.visit('/on/npm/')
self.visit_as('alice')
assert self.css('.highlight').text == 'Link an email'

def test_auth_without_claimable_packages_gets_disabled_apply_button(self):
self.make_participant('doug', claimed_time='now', email_address='[email protected]')
self.visit('/')
self.sign_in('doug')
self.visit('/on/npm/')

self.visit_as('doug')
button = self.css('.important-button button')
assert button.text == 'Apply to accept payments'
assert button['disabled'] == 'true'
Expand All @@ -183,10 +184,7 @@ def test_auth_with_claimable_packages_gets_apply_button(self):
alice = self.make_participant('alice', claimed_time='now',
email_address='[email protected]')
self.add_and_verify_email(alice, '[email protected]')
self.visit('/')
self.sign_in('alice')
self.visit('/on/npm/')

self.visit_as('alice')
button = self.css('.important-button button')
assert button.text == 'Apply to accept payments'
assert button['disabled'] is None
Expand All @@ -197,12 +195,43 @@ def test_differentiates_claimed_packages(self):
email_address='[email protected]')
Package.from_names(NPM, 'foo').get_or_create_linked_team(self.db, alice)
Package.from_names(NPM, 'bar').get_or_create_linked_team(self.db, bob)
self.visit('/')
self.sign_in('alice')
self.visit('/on/npm/')

self.visit_as('alice')
assert self.css('.i1').has_class('disabled')
assert self.css('.i1 .owner a').text == '~bob'
assert not self.css('.i2').has_class('disabled')
assert self.css('.i3').has_class('disabled')
assert self.css('.i3 .owner a').text == 'you'

def test_sends_mail(self):
self.make_participant('cat', claimed_time='now', email_address='[email protected]')
self.visit_as('cat')
self.css('.important-button button').click()
assert self.wait_for_success() == 'Check [email protected] for a verification link.'

def test_sends_one_mail_per_address(self):
cat = self.make_participant('cat', claimed_time='now', email_address='[email protected]')
self.add_and_verify_email(cat, '[email protected]')
self.visit_as('cat')
self.css('.important-button button').click()
assert self.wait_for_success() == 'Check [email protected] for a verification link.'
assert self.wait_for_success() == 'Check [email protected] for a verification link.'

def test_sends_one_mail_for_multiple_packages(self):
self.make_participant('alice', claimed_time='now', email_address='[email protected]')
self.visit_as('alice')
self.css('.important-button button').click()
assert len(self.css('table.listing td.item')) == 3
assert self.wait_for_success() == 'Check [email protected] for a verification link.'
assert self.db.one('select count(*) from claims') == 3
assert self.db.one('select count(*) from email_queue') == 1

def test_doesnt_send_for_unclaimable_packages(self):
self.make_participant('alice', claimed_time='now', email_address='[email protected]')
cat = self.make_participant('cat', claimed_time='now', email_address='[email protected]')
Package.from_names(NPM, 'baz').get_or_create_linked_team(self.db, cat)
self.visit_as('alice')
self.css('.important-button button').click()
assert len(self.css('table.listing td.item')) == 3
assert self.wait_for_success() == 'Check [email protected] for a verification link.'
assert self.db.one('select count(*) from claims') == 2
assert self.db.one('select count(*) from email_queue') == 1
2 changes: 1 addition & 1 deletion www/on/npm/%package/index.html.spt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if user.participant:
{% block scripts %}
<script>
$(document).ready(function() {
Gratipay.packages.init();
Gratipay.packages.initSingle();
});
</script>
{{ super() }}
Expand Down
25 changes: 23 additions & 2 deletions www/on/npm/index.html.spt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,30 @@ if user.participant:
any_claimable = any([rec.claimed_by is None for rec in packages_for_claiming])
[---]
{% extends "templates/base.html" %}

{% block banner %}
<a class="elsewhere" href="https://www.npmjs.com/">
<div class="avatar">
<img class="avatar" src="{{ website.asset('package-default-large.png') }}" />
<img class="platform" src="{{ website.asset('npm-n.png') }}" />
</div>
{{ super() }}
</a>
{% endblock %}

{% block scripts %}
<script>
$(document).ready(function() {
Gratipay.packages.initBulk();
});
</script>
{{ super() }}
{% endblock %}

{% block content %}

<p class="description important-thing-at-the-top">
{{ ('Free as in money.') }}
{{ _('Free as in money.') }}
</p>

<p class="instructions">
Expand All @@ -32,7 +52,8 @@ if user.participant:
<table class="listing">
{% for i, rec in enumerate(packages_for_claiming, start=1) %}
<tr>
<td class="item i{{i}}{% if rec.claimed_by %} disabled{% endif %}">
<td class="item i{{i}}{% if rec.claimed_by %} disabled{% endif %}"
data-email="{{ rec.email_address }}" data-package-id="{{ rec.package.id }}">
<img class="avatar" src="{{ website.asset('package-default-small.png') }}">
<div class="package-manager"><img src="{{ website.asset('npm-n.png') }}"></div>
<a class="listing-name" href="{{ rec.package.url_path }}">
Expand Down
7 changes: 5 additions & 2 deletions www/~/%username/emails/modify.json.spt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Manages the authenticated user's email addresses.
import re

from aspen import Response
from gratipay.exceptions import EmailTaken, EmailAlreadyVerified, Throttled
from gratipay.utils import get_participant
from gratipay.models.package import Package

Expand All @@ -18,6 +17,7 @@ participant = get_participant(state, restrict=True)

action = request.body['action']
address = request.body['address']
show_address_in_message = bool(request.body.get('show_address_in_message', ''))

# Basic checks. The real validation will happen when we send the email.
if (len(address) > 254) or not email_re.match(address):
Expand All @@ -44,7 +44,10 @@ if action in ('add-email', 'resend', 'start-verification'):
raise Response(400)

participant.start_email_verification(address, *packages)
msg = _("Check your inbox for a verification link.")
if show_address_in_message:
msg = _("Check {email_address} for a verification link.", email_address=address)
else:
msg = _("Check your inbox for a verification link.")
elif action == 'set-primary':
participant.set_primary_email(address)
elif action == 'remove':
Expand Down

0 comments on commit ebf2c95

Please sign in to comment.