Flesh out donation page

This commit is contained in:
Drew DeVault 2015-09-05 21:30:52 -04:00
parent 7a144f71f0
commit db7cd6dd17
13 changed files with 196 additions and 46 deletions

View File

@ -1,7 +1,6 @@
# Builds static assets # Builds static assets
# Depends on: # Depends on:
# - scss # - scss
# - coffeescript
# - inotify-tools # - inotify-tools
# Run `make` to compile static assets # Run `make` to compile static assets
# Run `make watch` to recompile whenever a change is made # Run `make watch` to recompile whenever a change is made
@ -10,8 +9,7 @@
STYLES:=$(patsubst styles/%.scss,static/%.css,$(wildcard styles/*.scss)) STYLES:=$(patsubst styles/%.scss,static/%.css,$(wildcard styles/*.scss))
STYLES+=$(patsubst styles/%.css,static/%.css,$(wildcard styles/*.css)) STYLES+=$(patsubst styles/%.css,static/%.css,$(wildcard styles/*.css))
SCRIPTS:=$(patsubst scripts/%.coffee,static/%.js,$(wildcard scripts/*.coffee)) SCRIPTS:=$(patsubst scripts/%.js,static/%.js,$(wildcard scripts/*.js))
SCRIPTS+=$(patsubst scripts/%.js,static/%.js,$(wildcard scripts/*.js))
_STATIC:=$(patsubst _static/%,static/%,$(wildcard _static/*)) _STATIC:=$(patsubst _static/%,static/%,$(wildcard _static/*))
static/%: _static/% static/%: _static/%
@ -30,10 +28,6 @@ static/%.js: scripts/%.js
@mkdir -p static/ @mkdir -p static/
cp $< $@ cp $< $@
static/%.js: scripts/%.coffee
@mkdir -p static/
coffee -m -o static/ -c $<
static: $(STYLES) $(SCRIPTS) $(_STATIC) static: $(STYLES) $(SCRIPTS) $(_STATIC)
all: static all: static

BIN
_static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

View File

@ -19,6 +19,7 @@ smtp-from=donate@you.com
# Your information # Your information
your_name=Joe Bloe your_name=Joe Bloe
your_email=joe@bloe.com your_email=joe@bloe.com
# ^ you should have a gravatar that works with this email
# SQL connection string # SQL connection string
connection-string=postgresql://postgres@localhost/fosspay connection-string=postgresql://postgres@localhost/fosspay

View File

@ -12,6 +12,8 @@ from fosspay.objects import User
from fosspay.common import * from fosspay.common import *
from fosspay.network import * from fosspay.network import *
import fosspay.stripe
from fosspay.blueprints.html import html from fosspay.blueprints.html import html
app = Flask(__name__) app = Flask(__name__)

View File

@ -7,6 +7,7 @@ from fosspay.config import _cfg, load_config
import locale import locale
import bcrypt import bcrypt
import hashlib
encoding = locale.getdefaultlocale()[1] encoding = locale.getdefaultlocale()[1]
html = Blueprint('html', __name__, template_folder='../../templates') html = Blueprint('html', __name__, template_folder='../../templates')
@ -17,7 +18,8 @@ def index():
load_config() load_config()
return render_template("setup.html") return render_template("setup.html")
projects = sorted(Project.query.all(), key=lambda p: p.name) projects = sorted(Project.query.all(), key=lambda p: p.name)
return render_template("index.html", projects=projects) avatar = "//www.gravatar.com/avatar/" + hashlib.md5(_cfg("your-email").encode("utf-8")).hexdigest()
return render_template("index.html", projects=projects, avatar=avatar)
@html.route("/setup", methods=["POST"]) @html.route("/setup", methods=["POST"])
def setup(): def setup():

View File

@ -58,12 +58,14 @@ class Donation(Base):
type = Column(ChoiceType(DonationType, impl=String())) type = Column(ChoiceType(DonationType, impl=String()))
amount = Column(Integer, nullable=False) amount = Column(Integer, nullable=False)
created = Column(DateTime, nullable=False) created = Column(DateTime, nullable=False)
emailed_about = Column(Boolean, nullable=False)
def __init__(self, user, type, amount): def __init__(self, user, type, amount):
self.user = user self.user = user
self.type = type self.type = type
self.amount = amount self.amount = amount
self.created = datetime.now() self.created = datetime.now()
self.emailed_about = False
def __repr__(self): def __repr__(self):
return "<Donation {} from {}: ${} ({})>".format( return "<Donation {} from {}: ${} ({})>".format(

6
fosspay/stripe.py Normal file
View File

@ -0,0 +1,6 @@
from fosspay.config import _cfg
import stripe
if _cfg("stripe-secret") != "":
stripe.api_key = _cfg("stripe-secret")

105
scripts/index.js Normal file
View File

@ -0,0 +1,105 @@
(function() {
var donation = {
type: "monthly",
amount: 1000, // cents
project: null,
comment: null
};
function selectAmount(e) {
e.preventDefault();
document.querySelector(".amounts .active").classList.remove("active");
e.target.classList.add("active");
var custom = document.querySelector("#custom-amount");
var amount = e.target.dataset.amount;
if (amount === "custom") {
custom.classList.remove("hidden");
donation.amount = +document.querySelector("#custom-amount-text").value * 100;
} else {
custom.classList.add("hidden");
donation.amount = +e.target.dataset.amount * 100;
}
}
function selectFrequency(e) {
e.preventDefault();
document.querySelector(".frequencies .active").classList.remove("active");
e.target.classList.add("active");
donation.type = e.target.dataset.frequency;
}
var amounts = document.querySelectorAll(".amounts button");
for (var i = 0; i < amounts.length; i++) {
amounts[i].addEventListener("click", selectAmount);
}
var frequencies = document.querySelectorAll(".frequencies button");
for (var i = 0; i < frequencies.length; i++) {
frequencies[i].addEventListener("click", selectFrequency);
}
document.getElementById("custom-amount-text").addEventListener("change", function(e) {
var value = +e.target.value;
if (isNaN(value)) {
value = 1;
}
e.target.value = value;
donation.amount = value * 100;
});
var project = document.getElementById("project")
if (project) {
project.addEventListener("change", function(e) {
if (e.target.value === "null") {
donation.project = null;
} else {
donation.project = e.target.value;
}
});
}
document.getElementById("donate-button").addEventListener("click", function(e) {
e.preventDefault();
if (e.target.getAttribute("disabled")) {
return;
}
var handler = StripeCheckout.configure({
name: your_name,
key: window.stripe_key,
image: window.avatar,
locale: 'auto',
description: donation.type == "monthly" ? "Monthly Donation" : "One-time Donation",
panelLabel: "Donate {{amount}}",
amount: donation.amount,
bitcoin: donation.type == "once",
token: function(token) {
e.target.setAttribute("disabled", "");
e.target.textContent = "Submitting...";
var data = new FormData();
data.append("stripe_token", token.id);
data.append("email", token.email);
data.append("amount", donation.amount);
data.append("type", donation.type);
data.append("comment", donation.comment);
if (donation.project !== null) {
data.append("project", donation.project);
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "donate");
xhr.onload = function() {
document.getElementById("donation-stuff").classList.add("hidden");
document.getElementById("thanks").classList.remove("hidden");
var res = JSON.parse(this.responseText);
if (res.newAccount) {
document.getElementById("new-donor-password").classList.remove("hidden");
}
};
xhr.send(data);
}
});
handler.open();
});
})();

View File

@ -1,9 +1,29 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block scripts %}
<script>
window.stripe_key = "{{ _cfg("stripe-publish") }}";
window.avatar = "{{ avatar }}";
window.your_name = "{{ _cfg("your-name") }}";
</script>
<script src="//checkout.stripe.com/checkout.js"></script>
<script src="static/index.js"></script>
{% endblock %}
{% block body %} {% block body %}
<div class="well"> <div class="well">
<div class="container"> <div class="container">
<h1>Donate to {{ _cfg("your-name") }}</h1> <h1>Donate to {{ _cfg("your-name") }}</h1>
<p><a href="#" data-toggle="modal" data-target="#how-this-works-modal">How does this work?</a></p> <div class="row">
<div class="col-md-8 col-md-offset-2">
<p>
Donations accumulate until there's enough to fund one week of
full time development. The project you specify influences which
projects receive the most time. Each donated-to project will
receive attention, even if there's just one donation for it.
Monthly donations will help me keep doing this for a long time,
but one-offs are also great.
</p>
</div>
</div>
</div> </div>
</div> </div>
<noscript> <noscript>
@ -14,25 +34,44 @@
</div> </div>
</div> </div>
</noscript> </noscript>
<div class="container text-center"> <div class="container text-center hidden" id="thanks">
{% include "post-donation-message.html" %}
<div id="new-donor-password" class="hidden">
<p>Set a password now if you want to manage your donations later:</p>
<input type="password" placeholder="Password" />
<button class="btn btn-primary btn-sm">Submit</button>
</div>
</div>
<div class="container text-center" id="donation-stuff">
<h3>How much?</h3> <h3>How much?</h3>
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<div class="btn-group btn-group-justified" role="group" aria-label="..."> <div class="btn-group btn-group-justified amounts" role="group" aria-label="...">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-default">$5</button> <button data-amount="5" type="button" class="btn btn-default">$5</button>
</div> </div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-default">$10</button> <button data-amount="10" type="button" class="btn btn-default active">$10</button>
</div> </div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-default">$20</button> <button data-amount="20" type="button" class="btn btn-default">$20</button>
</div> </div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-default">$50</button> <button data-amount="50" type="button" class="btn btn-default">$50</button>
</div> </div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button type="button" class="btn btn-default">Custom</button> <button data-amount="custom" type="button" class="btn btn-default">Custom</button>
</div>
</div>
</div>
</div>
<div class="row hidden" id="custom-amount" style="margin-top: 20px;">
<div class="col-md-4 col-md-offset-4">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">$</span>
<input id="custom-amount-text" type="text" value="1"
class="form-control" placeholder="Amount" />
</div> </div>
</div> </div>
</div> </div>
@ -40,12 +79,17 @@
<h3>How often?</h3> <h3>How often?</h3>
<div class="row"> <div class="row">
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<div class="btn-group btn-group-justified" role="group" aria-label="..."> <p class="text-muted"><small>Bitcoin is only supported for one-time donations.</small></p>
<div class="btn-group" role="group"> <div class="form-group">
<button type="button" class="btn btn-default">Once</button> <div class="btn-group btn-group-justified frequencies" role="group" aria-label="...">
</div> <div class="btn-group" role="group">
<div class="btn-group" role="group"> <button data-frequency="once" type="button"
<button type="button" class="btn btn-default">Monthly</button> class="btn btn-default">Once</button>
</div>
<div class="btn-group" role="group">
<button data-frequency="monthly" type="button"
class="btn btn-default active">Monthly</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -55,8 +99,8 @@
<div class="row"> <div class="row">
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<div class="form-group"> <div class="form-group">
<select> <select id="project" class="form-control">
<option value="none">None in particular</option> <option value="null">None in particular</option>
{% for project in projects %} {% for project in projects %}
<option value="{{ project.id }}">{{ project.name }}</option> <option value="{{ project.id }}">{{ project.name }}</option>
{% endfor %} {% endfor %}
@ -68,13 +112,13 @@
<div class="row"> <div class="row">
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" placeholder="Any comments?" /> <input type="text" id="comments" class="form-control" placeholder="Any comments?" />
</div> </div>
</div> </div>
</div> </div>
<div class="row" style="margin-top: 50px"> <div class="row" style="margin-top: 50px">
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<button class="btn btn-block btn-success">Donate</button> <button class="btn btn-block btn-success" id="donate-button">Donate</button>
</div> </div>
</div> </div>
</div> </div>
@ -92,21 +136,4 @@
</small> </small>
</p> </p>
</div> </div>
<div class="modal fade" id="how-this-works-modal" tabindex="-1" role="dialog" aria-labelledby="how-label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="how-label">How does this work?</h4>
</div>
<div class="modal-body">
{% include "how-this-works.html" %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Dismiss</button>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,13 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" />
<link rel="icon" href="static/icon.png" type="image/png" />
{% block title %} {% block title %}
<title>Donate to {{_cfg("your-name")}}</title> <title>Donate to {{_cfg("your-name")}}</title>
{% endblock %} {% endblock %}
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> {% block styles %}{% endblock %}
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head> </head>
<body> <body>
{% block body %} {% block body %}
@ -15,5 +16,8 @@
{% endblock %} {% endblock %}
</div> </div>
{% endblock %} {% endblock %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
{% block scripts %}{% endblock %}
</body> </body>
</html> </html>

5
templates/not_found.html Normal file
View File

@ -0,0 +1,5 @@
{% extends "layout.html" %}
{% block container %}
<h1>404 Not Found</h1>
<p><a href="/">Trying to donate?</a></p>
{% endblock %}

View File

@ -0,0 +1,2 @@
<h3>Thanks!</h3>
<p>You'll get an email when your money is going to be put to work.</p>

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block container %}
<h1>FossPay Setup</h1> <h1>FossPay Setup</h1>
<p>Congrats! You have FossPay up and running.</p> <p>Congrats! You have FossPay up and running.</p>