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

Commit

Permalink
Showing 28 changed files with 893 additions and 1,158 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
language: python
addons:
postgresql: 9.3
branches:
only:
- master
before_install:
- git branch -vv | grep '^*'
- pwd
33 changes: 14 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
#Contributing to Gratipay

##What is "open work"?
At http://gratipay.com/about, we have a definition:

> “Open work” means that your company or organization makes it easy for any individual to do your work for you without asking you first, and as a result to share in any revenue you generate.
Do work first, ask permission later. Fun, isn't it? If you are opening a new issue or submitting a pull request, **go for it!** Don't be afraid that it's a dumb idea or a duplicate of another issue or an unwanted change or whatever. Maybe it is! We're still glad to have you! :^)
If you are opening a new issue or submitting a pull request, **go for it!** Don't be afraid that it's a dumb idea or a duplicate of another issue or an unwanted change or whatever. Maybe it is! We're still glad to have you! 💃

##How you can contribute
Whether you are a programmer or not, you can contribute to Gratipay. Below are some ideas we've modified from [this great blog post](http://blog.smartbear.com/programming/14-ways-to-contribute-to-open-source-without-being-a-programming-genius-or-a-rock-star/) (more details at the link). You can check out a similar list [here](http://24pullrequests.com/contributing). If you see anything below that doesn't make sense, _relax_--find something you understand and start from there.
Whether you are a programmer or not, you can contribute to Gratipay. Below are some ideas we've modified from [this great blog post](http://blog.smartbear.com/programming/14-ways-to-contribute-to-open-source-without-being-a-programming-genius-or-a-rock-star/) (more details at the link). You can check out a similar list [here](http://24pullrequests.com/contributing). If you see anything below that doesn't make sense, _relax_—find something you understand and start from there.

1. Follow along on GitHub. Looks like you're in the right place already! See all project repositories at http://github.com/gratipay.
2. Follow the [Gratipay blog](https://medium.com/gratipay-blog).
3. Diagnose a bug by creating an issue.
3. Suggest an improvement by creating an issue.
4. Close resolved issues ([here's the big list](https://github.com/issues?q=is%3Aopen+user%3Agratipay+sort%3Aupdated-desc)).
5. [Test the code](https://github.com/gratipay/gratipay.com/blob/master/README.md).
6. Fix a bug/resolve an issue.
7. Write a test.
8. Add a comment to the code.
11. Create an example.
12. Answer a question.
13. Write a blog post.
14. Improve the website (either [gratipay.com](http://gratipay.com) or [inside.gratipay.com](http://inside.gratipay.com)).
1. Follow the [Gratipay blog](https://medium.com/gratipay-blog).
1. Diagnose a bug by creating an issue.
1. Suggest an improvement by creating an issue.
1. Close resolved issues ([here's the big list](https://github.com/issues?q=is%3Aopen+user%3Agratipay+sort%3Aupdated-desc)).
1. [Test the code](https://github.com/gratipay/gratipay.com/blob/master/README.md).
1. Fix a bug/resolve an issue.
1. Write a test.
1. Add a comment to the code.
1. Create an example.
1. Answer a question.
1. Write a blog post.
1. Improve the website (either [gratipay.com](http://gratipay.com) or [inside.gratipay.com](http://inside.gratipay.com)).

###More info
If you want to really get involved, then check out our [full documentation for contributors](http://inside.gratipay.com/).
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
[![Build Status](http://img.shields.io/travis/gratipay/gratipay.com/master.svg)](https://travis-ci.org/gratipay/gratipay.com)
[![Open Bounties](https://api.bountysource.com/badge/team?team_id=423&style=bounties_received)](https://www.bountysource.com/teams/gratipay/issues)

[Gratipay](http://gratipay.com) provides payments and payouts for open work,
[Gratipay](http://gratipay.com) helps companies fund open source,
in order to cultivate an economy of gratitude, generosity, and love.


11 changes: 4 additions & 7 deletions gratipay/models/team/__init__.py
Original file line number Diff line number Diff line change
@@ -101,11 +101,9 @@ def insert(cls, owner, **fields):
INSERT INTO teams
(slug, slug_lower, name, homepage,
product_or_service, todo_url, onboarding_url,
owner)
product_or_service, owner)
VALUES (%(slug)s, %(slug_lower)s, %(name)s, %(homepage)s,
%(product_or_service)s, %(todo_url)s, %(onboarding_url)s,
%(owner)s)
%(product_or_service)s, %(owner)s)
RETURNING teams.*::teams
""", fields)
@@ -170,7 +168,7 @@ def get_payment_distribution(self):

def update(self, **kw):
updateable = frozenset(['name', 'product_or_service', 'homepage',
'onboarding_url', 'todo_url'])
'onboarding_url'])

cols, vals = zip(*kw.items())
assert set(cols).issubset(updateable)
@@ -320,8 +318,7 @@ def to_dict(self):
'owner': '~' + self.owner,
'receiving': self.receiving,
'slug': self.slug,
'status': self.status,
'todo_url': self.todo_url
'status': self.status
}


6 changes: 2 additions & 4 deletions gratipay/testing/harness.py
Original file line number Diff line number Diff line change
@@ -168,10 +168,10 @@ def make_team(self, *a, **kw):

team = self.db.one("""
INSERT INTO teams
(slug, slug_lower, name, homepage, product_or_service, todo_url,
(slug, slug_lower, name, homepage, product_or_service,
onboarding_url, owner, is_approved, available)
VALUES (%(slug)s, %(slug_lower)s, %(name)s, %(homepage)s, %(product_or_service)s,
%(todo_url)s, %(onboarding_url)s, %(owner)s, %(is_approved)s,
%(onboarding_url)s, %(owner)s, %(is_approved)s,
%(available)s)
RETURNING teams.*::teams
""", _kw)
@@ -321,5 +321,3 @@ def get_tip(self, tipper, tippee):
LIMIT 1
""", (tipper, tippee), back_as=dict, default=default)['amount']


2 changes: 1 addition & 1 deletion gratipay/utils/fake_data.py
Original file line number Diff line number Diff line change
@@ -142,6 +142,7 @@ def fake_team(db, teamowner, teamname=None):
teamname = faker.first_name() + fake_text_id(3)

ctime = teamowner.ctime + datetime.timedelta(days=7)

try:
teamslug = slugize(teamname)
homepage = 'http://www.example.org/' + fake_text_id(3)
@@ -153,7 +154,6 @@ def fake_team(db, teamowner, teamname=None):
, homepage=homepage
, ctime=ctime
, product_or_service=random.sample(productorservice,1)[0]
, todo_url=homepage + '/tickets'
, onboarding_url=homepage + '/contributing'
, owner=teamowner.username
, is_approved=random.sample(isapproved,1)[0]
5 changes: 5 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--https://github.com/gratipay/gratipay.com/pull/4214

BEGIN;
ALTER TABLE teams DROP COLUMN todo_url;
END;
4 changes: 1 addition & 3 deletions templates/about-features.html
Original file line number Diff line number Diff line change
@@ -2,9 +2,7 @@
{% block subnav %}
{% set current_page = request.path.raw.split('/')[3] %}
{% set nav_base = '/about/features' %}
{% set pages = [ ('/', _('Overview'))
, ('/payments', _('Payments'))
, ('/payroll', _('Payroll'))
{% set pages = [ ('/', _('Payments'))
, ('/dnt', _('Do Not Track'))
] %}
{% include "templates/nav.html" %}
7 changes: 2 additions & 5 deletions tests/py/test_pages.py
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ def test_twitter_associate(self):
assert self.client.GxT('/on/twitter/associate').code == 400

def test_about(self):
expected = "We provide voluntary"
expected = "We help companies pay"
actual = self.client.GET('/about/').body
assert expected in actual

@@ -134,10 +134,7 @@ def test_about_teams_redirect(self):
assert self.client.GxT('/about/features/teams/').code == 302

def test_about_payments(self):
assert "Payments" in self.client.GET('/about/features/payments').body.decode('utf8')

def test_about_payroll(self):
assert "Payroll" in self.client.GET('/about/features/payroll').body.decode('utf8')
assert "Payments" in self.client.GET('/about/features/').body.decode('utf8')

def test_404(self):
response = self.client.GET('/about/four-oh-four.html', raise_immediately=False)
80 changes: 43 additions & 37 deletions tests/py/test_team_edit.py
Original file line number Diff line number Diff line change
@@ -241,22 +241,19 @@ def test_edit(self):
'name': 'Enterprise',
'product_or_service': 'We save galaxies.',
'homepage': 'http://starwars-enterprise.com/',
'onboarding_url': 'http://starwars-enterprise.com/onboarding',
'todo_url': 'http://starwars-enterprise.com/todos',
'image': FileUpload(IMAGE, 'logo.png'),
}
data = json.loads(self.client.POST( '/enterprise/edit/edit.json'
, data=edit_data
, auth_as='picard').body)
, auth_as='picard'
).body)

team = T('enterprise')
assert data == team.to_dict()

assert team.name == 'Enterprise'
assert team.product_or_service == 'We save galaxies.'
assert team.homepage == 'http://starwars-enterprise.com/'
assert team.onboarding_url == 'http://starwars-enterprise.com/onboarding'
assert team.todo_url == 'http://starwars-enterprise.com/todos'
assert team.load_image('original') == IMAGE

def test_edit_supports_partial_updates(self):
@@ -267,21 +264,21 @@ def test_edit_supports_partial_updates(self):
'image': FileUpload(IMAGE, 'logo.png'),
}
self.client.POST( '/enterprise/edit/edit.json'
, data=edit_data
, auth_as='picard')
, data=edit_data
, auth_as='picard'
)

team = T('enterprise')
assert team.name == 'The Enterprise'
assert team.product_or_service == 'We save galaxies.'
assert team.homepage == 'http://starwars-enterprise.com/'
assert team.onboarding_url == ''
assert team.todo_url == ''
assert team.load_image('original') == IMAGE

def test_edit_needs_auth(self):
self.make_team(slug='enterprise', is_approved=True)
response = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' })
, data={'name': 'Enterprise'}
)
assert response.code == 401
assert T('enterprise').name == 'The Enterprise'

@@ -291,14 +288,16 @@ def test_only_admin_and_owner_can_edit(self):
self.make_team(slug='enterprise', is_approved=True)

response = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' }
, auth_as='alice')
, data={'name': 'Enterprise'}
, auth_as='alice'
)
assert response.code == 403
assert T('enterprise').name == 'The Enterprise'

response = self.client.POST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' }
, auth_as='admin')
, data={'name': 'Enterprise'}
, auth_as='admin'
)
assert response.code == 200
assert T('enterprise').name == 'Enterprise'

@@ -309,39 +308,43 @@ def test_cant_edit_closed_teams(self):
self.db.run("UPDATE teams SET is_closed = true WHERE slug = 'enterprise'")

response = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' }
, auth_as='picard')
, data={'name': 'Enterprise'}
, auth_as='picard'
)
assert response.code in (403, 410)
assert T('enterprise').name == 'The Enterprise'

def test_cant_edit_rejected_teams(self):
self.make_team(slug='enterprise', is_approved=False)
response = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' }
, auth_as='picard')
, data={'name': 'Enterprise'}
, auth_as='picard'
)
assert response.code == 403
assert T('enterprise').name == 'The Enterprise'

def test_can_edit_teams_under_review(self):
self.make_team(slug='enterprise', is_approved=None)
response = self.client.POST( '/enterprise/edit/edit.json'
, data={ 'name': 'Enterprise' }
, auth_as='picard')
, data={'name': 'Enterprise'}
, auth_as='picard'
)
assert response.code == 200
assert T('enterprise').name == 'Enterprise'

def test_can_only_edit_allowed_fields(self):
allowed_fields = set(['name', 'image', 'product_or_service',
'homepage', 'onboarding_url', 'todo_url'])
allowed_fields = set(['name', 'image', 'product_or_service', 'homepage'])

team = self.make_team(slug='enterprise', is_approved=None)

fields = vars(team).keys()
fields.remove('onboarding_url') # we are still keeping this in the db for now
for field in fields:
if field not in allowed_fields:
response = self.client.POST( '/enterprise/edit/edit.json'
, data={ field: 'foo' }
, auth_as='picard')
, data={field: 'foo'}
, auth_as='picard'
)
new_team = T('enterprise')
assert response.code == 200
assert getattr(new_team, field) == getattr(team, field)
@@ -351,30 +354,33 @@ def test_edit_accepts_jpeg_and_png(self):
image_types = ['png', 'jpg', 'jpeg']
for i_type in image_types:
team.save_image(original='', large='', small='', image_type='image/png')
data = { 'image': FileUpload(IMAGE, 'logo.'+i_type) }
data = {'image': FileUpload(IMAGE, 'logo.'+i_type)}
response = self.client.POST( '/enterprise/edit/edit.json'
, data=data
, auth_as='picard')
, data=data
, auth_as='picard'
)
assert response.code == 200
assert team.load_image('original') == IMAGE

def test_edit_with_invalid_image_type_raises_error(self):
team = self.make_team(slug='enterprise', is_approved=True)
invalid_image_types = ['tiff', 'gif', 'bmp', 'svg']
for i_type in invalid_image_types:
data = { 'image': FileUpload(IMAGE, 'logo.'+i_type) }
data = {'image': FileUpload(IMAGE, 'logo.'+i_type)}
response = self.client.PxST( '/enterprise/edit/edit.json'
, data=data
, auth_as='picard')
, data=data
, auth_as='picard'
)
assert response.code == 400
assert "Please upload a PNG or JPG image." in response.body
assert team.load_image('original') == None

def test_edit_with_empty_values_raises_error(self):
self.make_team(slug='enterprise', is_approved=True)
response = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'name': ' ' }
, auth_as='picard')
, data={'name': ' '}
, auth_as='picard'
)
assert response.code == 400
assert T('enterprise').name == 'The Enterprise'

@@ -384,8 +390,9 @@ def test_edit_with_bad_url_raises_error(self):
, homepage='http://starwars-enterprise.com/')

r = self.client.PxST( '/enterprise/edit/edit.json'
, data={ 'homepage': 'foo' }
, auth_as='picard')
, data={'homepage': 'foo'}
, auth_as='picard'
)
assert r.code == 400
assert "Please enter an http[s]:// URL for the 'Homepage' field." in r.body
assert T('enterprise').homepage == 'http://starwars-enterprise.com/'
@@ -397,13 +404,12 @@ def test_edit_with_empty_data_does_nothing(self):
'name': 'Enterprise',
'product_or_service': 'We save galaxies.',
'homepage': 'http://starwars-enterprise.com/',
'onboarding_url': 'http://starwars-enterprise.com/onboarding',
'todo_url': 'http://starwars-enterprise.com/todos',
}
self.make_team(**team_data)
r = self.client.POST( '/enterprise/edit/edit.json'
, data={}
, auth_as='picard')
, auth_as='picard'
)
assert r.code == 200

team = T('enterprise')
22 changes: 1 addition & 21 deletions tests/py/test_teams.py
Original file line number Diff line number Diff line change
@@ -170,7 +170,6 @@ class TestTeams(Harness):
'product_or_service': 'We make widgets.',
'homepage': 'http://gratipay.com/',
'onboarding_url': 'http://inside.gratipay.com/',
'todo_url': 'https://github.com/gratipay',
'agree_public': 'true',
'agree_payroll': 'true',
'agree_terms': 'true',
@@ -226,13 +225,9 @@ def test_casing_of_urls_survives(self):
self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='')
self.post_new(dict( self.valid_data
, homepage='Http://gratipay.com/'
, onboarding_url='http://INSIDE.GRATipay.com/'
, todo_url='hTTPS://github.com/GRATIPAY'
))
team = T('gratiteam')
assert team.homepage == 'Http://gratipay.com/'
assert team.onboarding_url == 'http://INSIDE.GRATipay.com/'
assert team.todo_url == 'hTTPS://github.com/GRATIPAY'

def test_casing_of_slug_survives(self):
self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='')
@@ -267,14 +262,6 @@ def test_error_message_for_public_review(self):
assert self.db.one("SELECT COUNT(*) FROM teams") == 0
assert "Sorry, you must agree to have your application publicly reviewed." in r.body

def test_error_message_for_payroll(self):
self.make_participant('alice', claimed_time='now', email_address='alice@example.com', last_paypal_result='')
data = dict(self.valid_data)
del data['agree_payroll']
r = self.post_new(data, expected=400)
assert self.db.one("SELECT COUNT(*) FROM teams") == 0
assert "Sorry, you must agree to be responsible for payroll." in r.body

def test_error_message_for_terms(self):
self.make_participant('alice', claimed_time='now', email_address='alice@example.com', last_paypal_result='')
data = dict(self.valid_data)
@@ -298,12 +285,6 @@ def test_error_message_for_bad_url(self):
assert self.db.one("SELECT COUNT(*) FROM teams") == 0
assert "Please enter an http[s]:// URL for the 'Homepage' field." in r.body

r = self.post_new(dict(self.valid_data, onboarding_url='foo'), expected=400)
assert "an http[s]:// URL for the 'Self-onboarding Documentation URL' field." in r.body

r = self.post_new(dict(self.valid_data, todo_url='foo'), expected=400)
assert "Please enter an http[s]:// URL for the 'To-do URL' field." in r.body

def test_error_message_for_invalid_team_name(self):
self.make_participant('alice', claimed_time='now', email_address='alice@example.com', last_paypal_result='')
data = dict(self.valid_data)
@@ -451,7 +432,6 @@ def test_update_works(self):
'product_or_service': 'We save galaxies.',
'homepage': 'http://starwars-enterprise.com/',
'onboarding_url': 'http://starwars-enterprise.com/onboarding',
'todo_url': 'http://starwars-enterprise.com/todos',
}
team.update(**update_data)
team = T('enterprise')
@@ -460,7 +440,7 @@ def test_update_works(self):

def test_can_only_update_allowed_fields(self):
allowed_fields = set(['name', 'product_or_service', 'homepage',
'onboarding_url', 'todo_url'])
'onboarding_url',])

team = self.make_team(slug='enterprise')

4 changes: 2 additions & 2 deletions tests/ttw/test_homepage.py
Original file line number Diff line number Diff line change
@@ -7,14 +7,14 @@
class Tests(BrowserHarness):

def test_homepage_renders_copy_correctly_for_anon(self):
assert self.css('#content h1').html == 'Teams'
assert self.css('#content h1').html == 'Projects'
assert self.css('#header .sign-in button').html.strip()[:7] == 'Sign in'

def test_homepage_renders_copy_correctly_for_authed(self):
self.make_participant('alice', claimed_time='now')
self.sign_in('alice')
self.reload()
assert self.css('#content h1').html == 'Teams'
assert self.css('#content h1').html == 'Projects'
assert self.css('.you-are a').html.strip()[:6] == '~alice'

def test_homepage_pops_the_welcome_modal_for_first_time_visitors(self):
4 changes: 1 addition & 3 deletions www/%team/edit/edit.json.spt
Original file line number Diff line number Diff line change
@@ -21,7 +21,6 @@ field_names = {
'product_or_service': 'Product or Service',
'homepage': 'Homepage',
'onboarding_url': 'Self-onboarding Documentation URL',
'todo_url': 'To-do URL',
}

if user.ANON:
@@ -54,8 +53,7 @@ for field in data.keys():
if not value:
raise Response(400, _("Please fill out the '{}' field.", field_names[field]))

if (field in ('homepage', 'onboarding_url', 'todo_url')
and not valid_url(value)):
if field == 'homepage' and not valid_url(value):
raise Response(400,
_( "Please enter an http[s]:// URL for the '{}' field."
, field_names[field]
16 changes: 5 additions & 11 deletions www/%team/edit/index.html.spt
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ if team.is_closed:
if team.is_approved is False: # for teams under review, is_approved is None.
raise Response(403, _("You can't edit a rejected team."))

title = _("Edit your team")
title = _("Edit Your Project")
banner = _("Edit")
suppress_sidebar = True

@@ -44,24 +44,18 @@ suppress_sidebar = True
<form action="edit.json" method="POST" id = "edit-team">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">

<label><h2>{{ _("Team Name") }}</h2></label>
<label><h2>{{ _("Project Name") }}</h2></label>
<input type="text" name="name" value="{{team.name}}" required autofocus>

<label><h2>{{ _("Image") }}</h2></label>
<img src="{{ team.get_image_url('small') }}" align="middle">
<input type="file" name="image" accept="image/png, image/jpeg">

<label><h2>{{ _("Product or Service") }}</h2></label>
<textarea name="product_or_service" required>{{team.product_or_service}}</textarea>

<label><h2>{{ _("Homepage") }}</h2></label>
<input type="text" name="homepage" value="{{team.homepage}}" required>

<label><h2>{{ _("Self-onboarding Documentation URL") }}</h2></label>
<input type="text" name="onboarding_url" value="{{team.onboarding_url}}" required>

<label><h2>{{ _("To-do URL") }}</h2></label>
<input type="text" name="todo_url" value="{{team.todo_url}}" required>
<label><h2>{{ _("Image") }}</h2></label>
<img src="{{ team.get_image_url('small') }}" align="middle">
<input type="file" name="image" accept="image/png, image/jpeg">

<br>
<br>
8 changes: 0 additions & 8 deletions www/%team/index.html.spt
Original file line number Diff line number Diff line change
@@ -41,14 +41,6 @@ is_team_owner = not user.ANON and team.owner == user.participant.username

<a href="{{ team.homepage }}">{{ _("Homepage") }}</a>

{% if team.onboarding_url %}
| <a href="{{ team.onboarding_url }}">{{ _("Onboarding") }}</a>
{% endif %}

{% if team.todo_url %}
| <a href="{{ team.todo_url }}">{{ _("To-do") }}</a>
{% endif %}

{% if user.ADMIN or is_team_owner %}
|{{ _( "{a} Edit team {_a}"
, a='<a href="./edit">'|safe
155 changes: 138 additions & 17 deletions www/about/features/index.spt
Original file line number Diff line number Diff line change
@@ -5,31 +5,152 @@ title = _("Features")
{% extends "templates/about-features.html" %}
{% block content %}

<p>Gratipay provides voluntary <a
href="/about/features/payments">payments</a> and (eventually) <a
href="/about/features/payroll">payroll</a> for open work. Read our <a
href="/about/">site overview</a> for basic definitions.</p>
<p>Gratipay offers a <a
href="https://en.wikipedia.org/wiki/Pay_what_you_want">voluntary</a> <a
href="https://en.wikipedia.org/wiki/Subscription_business_model">subscription</a>
<a href="https://en.wikipedia.org/wiki/Revenue_model">revenue model</a>,
wherein projects receive weekly payments from companies, organizations, and
individuals.</p>


<h2>How do Payments Work?</h2>
<h2 id="receiving">How do I use Gratipay to receive money?
<a class="permalink" title="permalink" href="#receiving">&#182;</a>
</h2>

<p><a href="/new">Apply for your project</a> to join Gratipay.</p>


<h2 id="giving">How do I give money?
<a class="permalink" title="permalink" href="#giving">&#182;</a>
</h2>

<p><a href="/explore/teams/">Browse</a> or <a href="/search">search</a> for a
project you want to give to, and then look for the prompt. Here's <a
href="/introtopython/">introtopython</a>, for example:</p>

<div><a href="/introtopython/"><img src="giving-prompt-anon.png"></a></div>

<p>Then once you sign in:</p>

<div><a href="/introtopython/"><img src="giving-prompt-auth.png"></a></div>


<h2 id="when">When are payments run?
<a class="permalink" title="permalink" href="#when">&#182;</a>
</h2>

<p>We process payments once a week, on Thursdays. You can <a
href="https://github.com/gratipay/inside.gratipay.com/issues?q=label%3APayday">follow
along</a> each week.</p>


<h2 id="currencies">What currencies do you support?
<a class="permalink" title="permalink" href="#currencies">&#182;</a>
</h2>

<p>We charge and pay out in USD.</p>


<h2 id="methods">What payment methods do you support?
<a class="permalink" title="permalink" href="#methods">&#182;</a>
</h2>

<p>We are able to charge these brands of credit and debit card: Visa,
MasterCard, Discover, JCB, and American Express.</p>

<p>We pay out via PayPal.</p>


<h2 id="fees">What are the payment fees?
<a class="permalink" title="permalink" href="#fees">&#182;</a>
</h2>

See <a href="/about/pricing">pricing</a>.


<h2 id="limits">What are the payment limits?
<a class="permalink" title="permalink" href="#limits">&#182;</a>
</h2>

<p>The minimum you can give any one project is 1&#162; per week. The maximum is
$1,000 per week, and is intended to reduce income volatility by reducing how
dependent a project is on a few sources of income.</p>

<p>Our minimum credit/debit card charge is $10. We <a
href="https://medium.com/gratipay-blog/charging-in-arrears-18cacf779bee#.z92kxeqt6">charge
in arrears</a>. For example, we'll charge $10 (+ fees) at the end of ten weeks
for a $1/wk subscription. This may cause your project's income to <a
href="#receiving-less">fluctuate</a>.</p>


<h2 id="promoting">What can I do to promote my project?
<a class="permalink" title="permalink" href="#promoting">&#182;</a>
</h2>

<p>Check out <a href="http://tractionbook.com/"><i>Traction</i></a>!</p>


<h2 id="who">How do I see who is giving to my project?
<a class="permalink" title="permalink" href="#who">&#182;</a>
</h2>

<p>You can't. Payments are anonymous, to avoid the problem where people feel
overly entitled to special treatment because they've given your project a
little money.</p>

<p>You <em>can</em> see overall statistics for your project under
&ldquo;Receiving&rdquo; on your project page, including:</p>

<ul>
<li>Funded Dues: incoming payments from givers who have not reached the <a href="#limits">charge threshold</a>, backed by working credit cards.</li>
<li>Unfunded Dues: incoming payments that are not backed by working credit cards.</li>
<li>Estimated payment for next week: payments that the project will receive next <a href="#when">payday</a>.</li>
</ul>

<h2 id="receiving-less">Why is my project receiving less now than it was before?
<a class="permalink" title="permalink" href="#receiving-less">&#182;</a>
</h2>

<p>If your project's income on Gratipay decreased, one of the following
happened (most likely first):</p>

<ol>
<li>Due to the way we handle <a href="#limits">minimum charges</a>, it's
possible that (for example) you might see a $10 payment every 10
weeks.</li>

<li>Someone decreased or entirely removed their payment to your project, or
their Gratipay account was closed. As Gratipay payments are anonymous, we
cannot tell you who.</li>

<li>Someone's credit card failed when we tried to charge it.</li>

<li>You have violated our <a href="https://gratipay.com/about/terms/">terms
of service</a>. We will have contacted you before terminating your
account.</li>

<li>We received a legal document ordering Gratipay to cease processing
payments for you. We will contact you unless prohibited by court
order.</li>

</ol>

<p>Gratipay implements a <a
href="https://en.wikipedia.org/wiki/Pay_what_you_want">voluntary</a> <a
href="https://en.wikipedia.org/wiki/Subscription_business_model">subscription</a>
<a href="https://en.wikipedia.org/wiki/Revenue_model">revenue model</a>.
Payments are pay-what-you-want, and recurring.</p>

<p><a href="./payments">Read more about payments</a>.</p>
<h2 id="taxes">Is money I give through Gratipay tax-deductible?
<a class="permalink" title="permalink" href="#taxes">&#182;</a>
</h2>

<p>No.</p>

<h2>How does Payroll Work?</h2>

<p>To be honest ... it doesn't right now. But when we bring it back again,
it will be based on the idea that everyone sets their own salary. Our
payments are pay-what-you-want, and our payroll is
&ldquo;take-what-you-want.&rdquo;</p>
<h2 id="right-project">How do I know I am giving to the right project?
<a class="permalink" title="permalink" href="#right-project">&#182;</a>
</h2>

<p><a href="./payroll">Read more about payroll</a>.</p>
<p>You can follow links to Gratipay from a trusted source like a company or
organization's website. Also, Twitter verifies accounts in instances where this
is particularly relevant, so check their Twitter account carefully if it's
linked.</p>


<h2>Other Features</h2>
159 changes: 2 additions & 157 deletions www/about/features/payments.spt
Original file line number Diff line number Diff line change
@@ -1,158 +1,3 @@
[---]
banner = _("About")
title = _("Payments")
[---] text/html
{% extends "templates/about-features.html" %}
{% block content %}

<p>Gratipay offers a <a
href="https://en.wikipedia.org/wiki/Pay_what_you_want">voluntary</a> <a
href="https://en.wikipedia.org/wiki/Subscription_business_model">subscription</a>
<a href="https://en.wikipedia.org/wiki/Revenue_model">revenue model</a>,
wherein Teams receive weekly payments from ~users: see our <a href="/about/">site
overview</a> for basic definitions.</p>


<h2 id="receiving">How do I use Gratipay to receive money?
<a class="permalink" title="permalink" href="#receiving">&#182;</a>
</h2>

<p><a href="/new">Apply for a Team</a>. Your ~user account will be the
owner of the Team, and will take 100% of the Team's revenue each week
until we bring back <a href="./payroll">payroll</a>.</p>


<h2 id="giving">How do I give money?
<a class="permalink" title="permalink" href="#giving">&#182;</a>
</h2>

<p><a href="/explore/teams/">Browse</a> or <a href="/search">search</a> for
a Team you want to give to, and then look for the prompt. Here's <a
href="/introtopython/">introtopython</a>, for example:</p>

<div><a href="/introtopython/"><img src="giving-prompt-anon.png"></a></div>

<div><a href="/introtopython/"><img src="giving-prompt-auth.png"></a></div>


<h2 id="when">When are payments run?
<a class="permalink" title="permalink" href="#when">&#182;</a>
</h2>

<p>We process payments once a week, on Thursdays. You can <a
href="https://github.com/gratipay/inside.gratipay.com/issues?q=label%3APayday">watch
along</a> each week.</p>


<h2 id="currencies">What currencies do you support?
<a class="permalink" title="permalink" href="#currencies">&#182;</a>
</h2>

<p>We charge in USD.</p>


<h2 id="methods">What payment methods do you support?
<a class="permalink" title="permalink" href="#methods">&#182;</a>
</h2>

<p>We support the following brands of credit and debit card: Visa,
MasterCard, Discover, JCB, and American Express.</p>


<h2 id="fees">What are the payment fees?
<a class="permalink" title="permalink" href="#fees">&#182;</a>
</h2>

<p>Our credit card processor, Braintree, charges 2.9% + 30&#162; per
transaction. We upcharge ~users to cover this fee at cost, and Teams
receive the face value of the payment. For example, we'll charge $20.91 to
cover a $20 payment.</p>

<p>Non-U.S. banks may assess foreign exchange and/or other fees.</p>

<p>Gratipay itself is <a href="/Gratipay/">funded on Gratipay</a>. We don't
take a cut.<p>


<h2 id="limits">What are the payment limits?
<a class="permalink" title="permalink" href="#limits">&#182;</a>
</h2>

<p>The minimum a ~user can give any one Team is 1&#162; per week. The
maximum is $1,000 per week, and is intended to reduce income volatility by
reducing how dependent a Team is on a few sources of income.</p>

<p>Our minimum credit/debit card charge is $10. We <a
href="https://medium.com/gratipay-blog/charging-in-arrears-18cacf779bee#.z92kxeqt6">charge
in arrears</a>. For example, we'll charge $10 (+ fees) at the end of ten
weeks for a $1/wk subscription. This may cause your Team's receiving to <a href="#receiving-less">fluctuate</a>.</p>


<h2 id="promoting">What can I do to promote my Gratipay Team?
<a class="permalink" title="permalink" href="#promoting">&#182;</a>
</h2>

<p>Check out <a href="http://tractionbook.com/"><i>Traction</i></a>!</p>


<h2 id="who">How do I see who is giving to my Team?
<a class="permalink" title="permalink" href="#who">&#182;</a>
</h2>

<p>You can't. Payments are anonymous, to avoid the problem where people
feel overly entitled to special treatment because they've given your Team a
little money.</p>

<p>You <em>can</em> see overall statistics for your Team under "Receiving" on your Team page, including:</p>
<ul>
<li>Funded Dues: incoming payments from ~users who have not reached the <a href="#limits">charge threshold</a>, backed by working credit cards.</li>
<li>Unfunded Dues: incoming payments that are not backed by working credit cards.</li>
<li>Estimated payment for next week: payments that the Team will receive next <a href="#when">payday</a>.</li>
</ul>

<h2 id="receiving-less">Why is my Team receiving less now than it was before?
<a class="permalink" title="permalink" href="#receiving-less">&#182;</a>
</h2>

<p>If your Team's income on Gratipay decreased, one of the following
happened (most likely first):</p>

<ol>
<li>Due to the way we handle <a href="#limits">minimum charges</a>, it's
possible that (for example) you might see a $10 payment every 10
weeks.</li>

<li>A ~user decreased or entirely removed their payment to your Team,
or their Gratipay account was closed. As Gratipay payments are
anonymous, we cannot tell you who.</li>

<li>A ~user's credit card failed when we tried to charge it.</li>

<li>You have violated our <a
href="https://gratipay.com/about/terms/">terms of service</a>. We will
have contacted you before terminating your account.</li>

<li>We received a legal document ordering Gratipay to cease
processing payments for you. We will contact you unless prohibited by
court order.</li>

</ol>


<h2 id="taxes">Is money I give through Gratipay tax-deductible?
<a class="permalink" title="permalink" href="#taxes">&#182;</a>
</h2>

<p>No.</p>


<h2 id="right-team">How do I know I am giving to the right Team?
<a class="permalink" title="permalink" href="#right-team">&#182;</a>
</h2>

<p>You can follow links to Gratipay from a trusted source like a company
or organization's website. Also, Twitter verifies accounts in instances
where this is particularly relevant, so check their Twitter account
carefully if it's linked.</p>

{% endblock %}
website.redirect('/about/features/')
[---]
185 changes: 2 additions & 183 deletions www/about/features/payroll.spt
Original file line number Diff line number Diff line change
@@ -1,184 +1,3 @@
[---]
banner = _("About")
title = _("Payroll")
[---] text/html
{% extends "templates/about-features.html" %}
{% block content %}

<p>We've invented a system called &ldquo;Team takes</a>&rdquo; to solve the
<a
href="https://mako.cc/writing/funding_volunteers/funding_volunteers.html">problem
of compensating voluntary labor</a>. Essentially, in our solution, everyone
sets their own compensation. Our first version of this system worked great! Now
we are <a
href="https://github.com/gratipay/gratipay.com/issues/3433">revamping</a> it to
better fit with existing tax and labor law. <a
href="https://medium.com/gratipay-blog">Stay tuned</a>! :-)</p>

<a href="https://github.com/gratipay/gratipay.com/issues/3433">
<img src="{{ website.asset('under-construction.png') }}" width="386">
</a>

<p>This page documents how our system will work once we <a
href="https://github.com/gratipay/gratipay.com/issues/3433">bring it
back</a>, and the limited sense it which it works today. See our <a
href="/about/">site overview</a> for definitions of basic terms.</p>


<h2 id="take-what-you-want">Take-What-You-Want
</h2>

<p>The essence of Gratipay's payroll system is that everyone publicly sets
their own compensation, or &ldquo;take.&rdquo; Similar to <a
href="https://en.wikipedia.org/wiki/Pay_what_you_want">pay-what-you-want</a>
on the <a href="./payments">payments</a> side, Gratipay introduces
&ldquo;take-what-you-want&rdquo; on the payroll side.</p>

<p>The owner of a Team adds ~users as &ldquo;members,&rdquo; which sets
their weekly take to a penny, and can remove members, which is a safeguard
against abuse of the system. However, once a ~user is a member of a Team,
only that ~user can increase their take above a penny a week, or reduce it
once it's been increased (apart from the nuclear option of removing the
~user).</p>

<p>Gratipay's payroll system optimizes compensation of voluntary labor. In
a voluntary organization&mdash;by definition&mdash;each member has control
of their labor inputs. Gratipay payroll complements this with control over
their monetary withdrawals. By giving the individual control over
<i>both</i> variables, Gratipay provides for the resolution of the
individual resentment and guilt that otherwise build up when mixing money
with voluntary labor. Feeling burned out? Take more money, or do less work.
Not pulling your weight? Take less money, or do more work.</p>

<p>It is normal for payroll to gradually get out of balance over time, for
some people to end up taking too much and others too little. Team managers
should view payroll imbalances as vital indicators of underlying social
issues on the Team, and address those issues at the social level. Once the
social issues are addressed, the payroll will naturally rebalance to
reflect the new social reality of the Team. A healthy Team is built on
trust, and trusting each other with money is a powerful cause and effect of
mutual trust.</p>


<h2 id="law">What About Tax and Labor Law?
<a class="permalink" title="permalink" href="#law">&#182;</a>
</h2>

<p>Voluntary organizations such as open-source projects are often only
loosely organized. Owning a Gratipay Team, by contrast, introduces
weightier duties and responsibilities governed by tax and labor law. The
challenge we face is building out a system that is robust enough to be
legal, and simple enough to be accessible.</p>

<p>Team owners signing up now should be prepared to accept the
yet-to-be-fully-determined burden associated with payroll once we do <a
href="https://github.com/gratipay/gratipay.com/issues/3433">bring it
back</a>. <i>Caveat emptor</i>.</p>


<h2 id="not-enough-revenue">What if my Team doesn't have any revenue to share?
<a class="permalink" title="permalink" href="#not-enough-revenue">&#182;</a>
</h2>

<p>Gratipay's payroll is about sharing out of your Team's <i>abundance</i>,
so it doesn't apply if your Team has no revenue available to share with
contributors. Every Team when it's just starting out will take some time to
generate enough revenue to compensate people for their labor. Some Teams
may take especially long, for example, a coworking space where the money
primarily goes towards rent and other expenses. A good rule of thumb is
that if anyone <i>is</i> taking payroll for their labor, then anyone else
<i>may</i> take payroll for their labor.</p>


<h2 id="take-order">Who takes first?
<a class="permalink" title="permalink" href="#take-order">&#182;</a>
</h2>

<p>In our original experiment, the last person added to a Team was the
first person to take payroll each week (and take growth was throttled so
they couldn't take all the money and run). We'll probably add more
flexibility here so that company founders can be guaranteed they'll get
their take before sharing with others.</p>


<h2 id="distributing">How do I use Gratipay to distribute payroll?
<a class="permalink" title="permalink" href="#distributing">&#182;</a>
</h2>

<p><a href="/new">Apply for a Team</a>. Your ~user account will be the
owner of the Team, and will receive 100% of the Team's <a
href="./payments">revenue</a> each week.</p>


<h2 id="taking">How do I take money from a Team?
<a class="permalink" title="permalink" href="#taking">&#182;</a>
</h2>

<p>Right now, the only one who can take money from a Team is the Team's
owner. The Team's owner takes 100% of the Team's <a
href="./payments">revenue</a> each week.</p>


<h2 id="adding-members">How do I add members to my Team's payroll?
<a class="permalink" title="permalink" href="#adding-members">&#182;</a>
</h2>

<p>You <a href="https://github.com/gratipay/gratipay.com/issues/3433">can't
yet</a>.</p>


<h2 id="when">When is payroll run?
<a class="permalink" title="permalink" href="#when">&#182;</a>
</h2>

<p>We process payroll once a week, on Thursdays. You can <a
href="https://github.com/gratipay/inside.gratipay.com/issues?q=label%3APayday">watch
along</a> each week.</p>


<h2 id="currencies">What currencies do you support?
<a class="permalink" title="permalink" href="#currencies">&#182;</a>
</h2>

<p>We pay out in USD.</p>


<h2 id="methods">What payout methods do you support?
<a class="permalink" title="permalink" href="#methods">&#182;</a>
</h2>

<p>We are able to deliver funds to Team owners via PayPal. In fact, you
must connect a PayPal account to your ~user account via <a
href="/about/me/routes/">Gratipay Payment Routes page</a> before you can
apply for a Team.</p>


<h2 id="fees">What are the payout fees?
<a class="permalink" title="permalink" href="#fees">&#182;</a>
</h2>

<p>PayPal charges a 2% fee, capped at a $1 fee for U.S. accounts, and $20
for non-U.S. accounts. We assess this fee at cost upon payout.</p>

<p>There may be foreign exchange and/or other fees involved in withdrawing
funds from PayPal to your bank.</p>

<p>Gratipay itself is <a href="/Gratipay/">funded on Gratipay</a>. We don't
take a cut.<p>


<h2 id="limits">What are the payout limits?
<a class="permalink" title="permalink" href="#minimum">&#182;</a>
</h2>

<p>Our minimum payout is 50&#162;. We pool your money for you week to week
until you reach the minimum. There is no maximum.</p>


<h2 id="taxes">Do I have to pay taxes on the income I receive from Gratipay?
<a class="permalink" title="permalink" href="#taxes">&#182;</a>
</h2>

<p>Yes.</p>

{% endblock %}
website.redirect('/about/features/')
[---]
74 changes: 13 additions & 61 deletions www/about/index.spt
Original file line number Diff line number Diff line change
@@ -5,73 +5,25 @@ title = _("Welcome!")
{% extends "templates/about-basic-info.html" %}
{% block content %}

<p>Welcome to Gratipay! We provide voluntary <a
href="/about/features/payments">payments</a> and (eventually) <a
href="/about/features/payroll">payroll</a> for open work.</p>
<p>Welcome to Gratipay! We help companies pay open source projects.</p>

<p>Does your company use open source software? Probably. Open source software
is great! But if too few of us pay the projects that so many of us depend on,
then we have [a free rider
problem](http://www.fordfoundation.org/library/reports-and-studies/roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure/).
That's bad for all of us, because it's not sustainable. You can help by paying
open source projects on Gratipay!</p>

<h2 id="open-work">What is Open Work?
<a class="permalink" title="permalink" href="#open-work">&#182;</a>
</h2>

<p>&ldquo;Open work&rdquo; means that your company or organization makes it
easy for any individual to do your work for you without asking you first,
and as a result to share in any revenue you generate. Basically you need to
have a public issue tracker with documentation for self-onboarding, and be
willing to use our <a href="/about/features/payroll">payroll</a> feature
once we bring it back.</p>


<h2 id="terms">Other Basic Terminology
<a class="permalink" title="permalink" href="#terms">&#182;</a>
</h2>

<p>Gratipay has &ldquo;~users&rdquo; and &ldquo;Teams,&rdquo; and every
Team has an &ldquo;owner,&rdquo; who is a ~user.</p>

<img src="entities.svg">

<table class="table" style="margin-left: auto; margin-right: auto; white-space: nowrap;">
<tr>
<th>Speaking of ...&nbsp;&nbsp;&nbsp;</th>
<th>Subject</th>
<th>Verb</th>
<th>Object</th>
</tr>
<tr>
<td>payments</td>
<td>~alice</td>
<td><b>gives</b> to</td>
<td>The Enterprise.</td>
</tr>
<tr>
<td>payments</td>
<td>The Enterprise</td>
<td><b>receives</b> from</td>
<td>~alice.</td>
</tr>
<tr>
<td>payroll</td>
<td>The Enterprise</td>
<td><b>distributes</b> to</td>
<td>~crusher.</td>
</tr>
<tr>
<td>payroll</td>
<td>~crusher</td>
<td><b>takes</b> from</td>
<td>The Enterprise.</td>
</tr>
</table>
<br>
<p>Learn more about <a href="/about/features/">how payments work on
Gratipay</a>.


<h2 id="funding">How is Gratipay Funded?
<h2 id="funding">How does Gratipay make money?
<a class="permalink" title="permalink" href="#funding">&#182;</a>
</h2>

<p>Gratipay is <a href="/Gratipay/">funded on Gratipay</a>. We don't take a
cut of payments (though we do pass through processing fees at cost). See
our <a href="/about/pricing">pricing page</a> for more information.</p>
<p>Gratipay makes money <a href="/Gratipay/">on Gratipay</a>. We don't take a
cut of payments (though we do pass through processing fees at cost). See our <a
href="/about/pricing">pricing page</a> for more information.</p>

{% endblock %}
496 changes: 254 additions & 242 deletions www/about/policies/terms-of-service.md

Large diffs are not rendered by default.

496 changes: 265 additions & 231 deletions www/about/policies/terms-of-service.spt

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions www/about/pricing.spt
Original file line number Diff line number Diff line change
@@ -60,21 +60,25 @@ href="http://en.wikipedia.org/wiki/Pay_what_you_want">pay-what-you-want</a>.&rdq
<a class="permalink" title="permalink" href="#fees">&#182;</a>
</h2>

<p>We don't skim off the top, but we <i>do</i> charge you processing fees
when you use your credit card or other payment method, but that's at cost.
<p>Gratipay itself is <a href="/Gratipay/">funded on Gratipay</a>. We don't
skim off the top, but we <i>do</i> charge you processing fees when you use your
credit card or other payment method, but that's at cost.
It all goes to the payment processor. We don't see any of that
ourselves.</p>

<p>Read about processing fees for <a
href="/about/features/payments#fees">payments</a> and <a
href="/about/features/payroll#fees">payroll</a>.</p>
<p>Our credit card processor, Braintree, charges 2.9% + 30&#162; per
transaction. We upcharge givers to cover this fee at cost, so that projects receive
the face value of the payment. For example, we'll charge $20.91 to cover a $20
payment into Gratipay. Non-U.S. banks may assess foreign exchange and/or other fees.</p>

<p>Projects receive <a href="/about/features#receiving">payouts</a> via PayPal, which charges 2%.
This fee is passed on to the project on payday and is capped at $1 for U.S. PayPal
accounts.</p>

<h2 id="uses">Where Do Payments Go?
<a class="permalink" title="permalink" href="#uses">&#182;</a>
</h2>


<p>Your voluntary payments <a href="/Gratipay/">to Gratipay</a> cover
hosting and other costs of doing business, and the rest goes to the people
who are building Gratipay for you. If you want to help us build Gratipay,
14 changes: 7 additions & 7 deletions www/about/stats.spt
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ average_number_of_payments = average_number_of_payments or 0

{% block content %}

<p>{{ _( "Gratipay processes about {b}{volume} per week{_b} for {b}{nteams} Teams{_b} and about {b}{nusers} ~users{_b}."
<p>{{ _( "Gratipay processes about {b}{volume} per week{_b} for {b}{nteams} projects{_b} and about {b}{nusers} ~users{_b}."
, volume=format_currency(approximate(volume), 'USD', trailing_zeroes=False)
, nusers=approximate(nusers)
, nteams=nteams
@@ -49,8 +49,8 @@ average_number_of_payments = average_number_of_payments or 0
) }}</p>

<p>{{ ngettext(
"On average, ~users who give payments on Gratipay give {b}{amount} to one Team{_b}."
, "On average, ~users who give payments on Gratipay give {b}{amount} each to {number} Teams{_b}."
"On average, ~users who give on Gratipay give {b}{amount} to one project{_b}."
, "On average, ~users who give on Gratipay give {b}{amount} each to {number} projects{_b}."

, average_number_of_payments
, amount=format_currency(average_payment_amount, 'USD')
@@ -75,7 +75,7 @@ average_number_of_payments = average_number_of_payments or 0
<div class="chart-wrapper">
<a name="volume"></a>
<h2>Weekly Volume ($)</h2>
<p class="note">Total amount received by Teams on Gratipay (per week) *</p>
<p class="note">Total amount received by projects on Gratipay (per week) *</p>
<div class="chart" data-chart="volume"></div>
<div class="x-axis">weeks</div>
<p class="note">* Week 153 was the <a
@@ -85,7 +85,7 @@ average_number_of_payments = average_number_of_payments or 0
<div class="chart-wrapper">
<a name="users"></a>
<h2>Active ~users</h2>
<p class="note">~users that gave and/or took money on Gratipay (per week) *</p>
<p class="note">~users who gave and/or took money on Gratipay (per week) *</p>
<div class="chart" data-chart="nusers"></div>
<div class="x-axis">weeks</div>
<p class="note">* Week 153 was the <a
@@ -94,8 +94,8 @@ average_number_of_payments = average_number_of_payments or 0

<div class="chart-wrapper">
<a name="teams"></a>
<h2>Active Teams</h2>
<p class="note">Teams that received and shared money on Gratipay (per week) *</p>
<h2>Active Projects</h2>
<p class="note">Projects that received money on Gratipay (per week) *</p>
<div class="chart" data-chart="nteams"></div>
<div class="x-axis">weeks</div>
<p class="note">* <a href="https://medium.com/gratipay-blog/gratipay-2-0-2453d3c53077">Gratipay
86 changes: 86 additions & 0 deletions www/apply.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from datetime import datetime

from aspen import Response
from gratipay.models.team import Team
[---]
request.allow('GET')

if user.ANON:
raise Response(401, _("You must sign in to apply for your project to join Gratipay."))

if user.participant.email_address is None:
raise Response(400, _("You must have a verified email address to apply for your project to join Gratipay."))

if not user.participant.has_payout_route:
raise Response(400, _("You must attach a PayPal account to apply for your project to join Gratipay."))

title = _("Apply to Join Gratipay")
banner = _("Apply")
suppress_sidebar = True
[---] text/html
{% extends "templates/base.html" %}

{% block scripts %}
<script>$(document).ready(Gratipay.new_team.initForm);</script>
{{ super() }}
{% endblock %}

{% block content %}
<style>
textarea {
width: 100%;
height: 200px;
}
</style>
<div class="application-complete" style="display: none;">
<p>{{ _("Thanks! Your public project page is:") }}</p>
<p><a href="" class="team_url"></a></p>
<p>{{ _("And your public review ticket is:") }}</p>
<p><a href="" class="review_url"></a></p>
<p>{{ _( "You can watch and participate in our review process there. We will send a notification to {email} when we finish our review."
, email=user.participant.email_address
) }}</p>
<p>{{ _("Thanks for applying!") }}</p>
</div>
<form action="/teams/create.json" method="POST" id="new-team">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">

<label><h2>{{ _("Project Name") }}</h2></label>
<p><i>{{ _("At least one letter A through Z, plus numbers, dashes (-), underscores (_), periods (.), comma (,) and whitespace.") }}</i></p>
<input type="text" name="name" required autofocus>

<label><h2>{{ _("Product or Service") }}</h2></label>
<p><i>{{ _("What product or service does your project provide?") }}</i></p>
<textarea name="product_or_service" required></textarea>

<label><h2>{{ _("Homepage") }}</h2></label>
<p><i>{{ _("What is the customer-facing web page for your project?") }}</i></p>
<input type="text" name="homepage" required>

<label><h2>{{ _("Image") }}</h2></label>
<p><i>{{ _("PNG or JPG up to 256 kB. Will be cropped square and resized to 160 x 160 and 48 x 48 (with original saved for later).") }}</i></p>
<input type="file" name="image" required>

<h2>{{ _("Agreements") }}</h2>
<input type="checkbox" value="true" name="agree_public" id="agree_public">
<label for="agree_public">
{{ _( "I agree to have my application {0}publicly reviewed{1}."
, '<a href="http://inside.gratipay.com/howto/review-teams">'|safe
, '</a>'|safe
) }}
</label>

<br>
<input type="checkbox" value="true" name="agree_terms" id="agree_terms">
<label for="agree_terms">
{{ _( "I agree to the other {0}terms of service{1} as well."
, '<a href="/about/policies/terms-of-service">'|safe
, '</a>'|safe
) }}
</label>

<br>
<br>
<button type="submit">{{ _("Apply") }}</button>
</form>
{% endblock %}
12 changes: 6 additions & 6 deletions www/index.html.spt
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ for key, tab in tabs.items():
, n
)

title = _("Teams")
title = _("Projects")
suppress_sidebar = True
suppress_welcome = 'suppress-welcome' in request.cookie
page_id = "homepage"
@@ -51,7 +51,7 @@ page_id = "homepage"
{% block head %}
<link rel="publisher" href="https://plus.google.com/104524895706770139568">
<link type="application/opensearchdescription+xml" rel="search" href="/opensearch.osdd" />
<meta name="description" content="Voluntary payments and payroll for open work" />
<meta name="description" content="Pay Open Source Projects on Gratipay" />
<meta name="fb:app_id" content="229465400522758" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://gratipay.com/" />
@@ -61,7 +61,7 @@ page_id = "homepage"

{% block banner %}
<a href="/about/" class="homepage">
{{ _("Payments{br}and Payroll{br}for Open Work", br='<br>'|safe) }}<br>
{{ _("We help{br}companies pay{br}open source projects.", br='<br>'|safe) }}<br>
<u>{{ _("Read more") }}&hellip;</u>
</a>
</a>
@@ -71,11 +71,11 @@ page_id = "homepage"
{% if not suppress_welcome %}
<div class="welcome modal">
<p><b>{{ _("Welcome to Gratipay!") }}</b></p>
<p>{{ _( "We have {nteams} Teams receiving and sharing about {volume} each week."
<p>{{ _( "We have {nteams} projects receiving and sharing about {volume} each week."
, nteams=tabs['approved']['n']
, volume=format_currency(volume, 'USD', trailing_zeroes=False)
) }}</p>
<p>{{ _( "Continue to explore our Teams, or {a}read more about us{_a}."
<p>{{ _( "Continue to explore our projects, or {a}read more about us{_a}."
, a='<a href="/about/">'|safe
, _a='</a>'|safe
) }}</p>
@@ -86,7 +86,7 @@ page_id = "homepage"
{% endif %}

<form action="new" class="apply">
<button type="submit">{{ _("Apply for a New Team") }}</button>
<button type="submit">{{ _("Apply to Join") }}</button>
</form>

<div class="tabs">
105 changes: 2 additions & 103 deletions www/new.spt
Original file line number Diff line number Diff line change
@@ -1,104 +1,3 @@
from datetime import datetime

from aspen import Response
from gratipay.models.team import Team
[---]
request.allow('GET')

if user.ANON:
raise Response(401, _("You must sign in to apply for a new Team."))

if user.participant.email_address is None:
raise Response(400, _("You must have a verified email address to apply for a new Team."))

if not user.participant.has_payout_route:
raise Response(400, _("You must attach a PayPal account to apply for a new Team."))

title = _("Apply for a New Team")
banner = _("Apply")
suppress_sidebar = True
[---] text/html
{% extends "templates/base.html" %}

{% block scripts %}
<script>$(document).ready(Gratipay.new_team.initForm);</script>
{{ super() }}
{% endblock %}

{% block content %}
<style>
textarea {
width: 100%;
height: 200px;
}
</style>
<div class="application-complete" style="display: none;">
<p>{{ _("Thanks! Your public team page is:") }}</p>
<p><a href="" class="team_url"></a></p>
<p>{{ _("And your public review ticket is:") }}</p>
<p><a href="" class="review_url"></a></p>
<p>{{ _( "You can watch and participate in our review process there. We will send a notification to {email} when we finish our review."
, email=user.participant.email_address
) }}</p>
<p>{{ _("Thanks for applying!") }}</p>
</div>
<form action="/teams/create.json" method="POST" id="new-team">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">

<label><h2>{{ _("Team Name") }}</h2></label>
<p><i>{{ _("At least one letter A through Z, plus numbers, dashes (-), underscores (_), periods (.), comma (,) and whitespace.") }}</i></p>
<input type="text" name="name" required autofocus>

<label><h2>{{ _("Image") }}</h2></label>
<p><i>{{ _("PNG or JPG up to 256 kB. Will be cropped square and resized to 160 x 160 and 48 x 48 (with original saved for later).") }}</i></p>
<input type="file" name="image" required>

<label><h2>{{ _("Product or Service") }}</h2></label>
<p><i>{{ _("What product or service does your Team provide?") }}</i></p>
<textarea name="product_or_service" required></textarea>

<label><h2>{{ _("Homepage") }}</h2></label>
<p><i>{{ _("What is the customer-facing web page for your product or service?") }}</i></p>
<input type="text" name="homepage" required>

<label><h2>{{ _("Self-onboarding Documentation URL") }}</h2></label>
<p><i>{{ _("Where can people find instructions on how to do your work for you ({0}example{1})? "
, '<a href="https://github.com/gratipay/gratipay.com/blob/master/CONTRIBUTING.md">'|safe
, '</a>'|safe
) }}</i></p>
<input type="text" name="onboarding_url" required>

<label><h2>{{ _("To-do URL") }}</h2></label>
<p><i>{{ _("Where can people find a list of available work to do for your Team?") }}</i></p>
<input type="text" name="todo_url" required>

<h2>{{ _("Agreements") }}</h2>
<input type="checkbox" value="true" name="agree_public" id="agree_public">
<label for="agree_public">
{{ _( "I agree to have my application {0}publicly reviewed{1}."
, '<a href="http://inside.gratipay.com/howto/review-teams">'|safe
, '</a>'|safe
) }}
</label>

<br>
<input type="checkbox" value="true" name="agree_payroll" id="agree_payroll">
<label for="agree_payroll">
{{ _( "I agree to be responsible for {0}payroll{1}."
, '<a href="/about/features/payroll">'|safe
, '</a>'|safe
) }}
</label>

<br>
<input type="checkbox" value="true" name="agree_terms" id="agree_terms">
<label for="agree_terms">
{{ _( "I agree to the {0}terms of service{1}."
, '<a href="/about/policies/terms-of-service">'|safe
, '</a>'|safe
) }}
</label>

<button type="submit">{{ _("Apply") }}</button>
</form>
{% endblock %}
website.redirect('/apply')
[---]
33 changes: 23 additions & 10 deletions www/search.spt
Original file line number Diff line number Diff line change
@@ -14,16 +14,29 @@ if query:
q = strip_accents(query)

if action in (None, 'search_usernames'):
results['usernames'] = website.db.all("""
SELECT username, avatar_url, similarity(username, %(q)s) AS rank
FROM participants
WHERE username %% %(q)s
AND claimed_time IS NOT NULL
AND is_searchable
AND NOT is_closed
ORDER BY rank DESC, username
LIMIT 10
""", locals())
results['usernames'] = website.db.all("""
SELECT username, avatar_url, similarity(username, %(q)s) AS rank
FROM participants
WHERE username %% %(q)s
AND claimed_time IS NOT NULL
"""
+("" if user.ADMIN else " AND is_searchable")
+""" AND NOT is_closed
ORDER BY rank DESC, username
LIMIT 10
""", locals())

if user.ADMIN:
if action in (None, 'search_usernames'):
results['usernames'] = website.db.all("""
SELECT username, avatar_url, similarity(username, %(q)s) AS rank
FROM participants
WHERE username %% %(q)s
AND claimed_time IS NOT NULL
AND NOT is_closed
ORDER BY rank DESC, username
LIMIT 10
""", locals())

if user.ADMIN:
if action in (None, 'search_emails'):
13 changes: 3 additions & 10 deletions www/teams/create.json.spt
Original file line number Diff line number Diff line change
@@ -19,11 +19,9 @@ request.allow('POST')

field_names = {
'name': 'Team Name',
'image': 'Image',
'product_or_service': 'Product or Service',
'homepage': 'Homepage',
'onboarding_url': 'Self-onboarding Documentation URL',
'todo_url': 'To-do URL',
'image': 'Image',
}

if user.ANON:
@@ -40,8 +38,6 @@ or user.participant.is_closed: # sanity checks

if not request.body.get('agree_public', False):
raise Response(400, _("Sorry, you must agree to have your application publicly reviewed."))
if not request.body.get('agree_payroll', False):
raise Response(400, _("Sorry, you must agree to be responsible for payroll."))
if not request.body.get('agree_terms', False):
raise Response(400, _("Sorry, you must agree to the terms of service."))

@@ -64,11 +60,8 @@ if request.method == 'POST':
raise Response(400, _("Please fill out the '{}' field.", field_names[field]))
fields[field] = value

for field in ('homepage', 'onboarding_url', 'todo_url'):
if not any(map(fields[field].lower().startswith, ('http://', 'https://'))):
raise Response(400, _( "Please enter an http[s]:// URL for the '{}' field."
, field_names[field]
))
if not any(map(fields['homepage'].lower().startswith, ('http://', 'https://'))):
raise Response(400, _("Please enter an http[s]:// URL for the 'Homepage' field."))

try:
fields['slug'] = slugize(fields['name'])

0 comments on commit dbaee7c

Please sign in to comment.