diff --git a/Makefile b/Makefile index 0eaf634..397283e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ # Builds static assets # Depends on: # - scss -# - coffeescript # - inotify-tools # Run `make` to compile static assets # 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/%.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/%: _static/% @@ -30,10 +28,6 @@ static/%.js: scripts/%.js @mkdir -p static/ cp $< $@ -static/%.js: scripts/%.coffee - @mkdir -p static/ - coffee -m -o static/ -c $< - static: $(STYLES) $(SCRIPTS) $(_STATIC) all: static diff --git a/_static/icon.png b/_static/icon.png new file mode 100644 index 0000000..6df26be Binary files /dev/null and b/_static/icon.png differ diff --git a/config.ini.example b/config.ini.example index 4ec1046..84a86e9 100644 --- a/config.ini.example +++ b/config.ini.example @@ -19,6 +19,7 @@ smtp-from=donate@you.com # Your information your_name=Joe Bloe your_email=joe@bloe.com +# ^ you should have a gravatar that works with this email # SQL connection string connection-string=postgresql://postgres@localhost/fosspay diff --git a/fosspay/app.py b/fosspay/app.py index 1793b76..08f031d 100644 --- a/fosspay/app.py +++ b/fosspay/app.py @@ -12,6 +12,8 @@ from fosspay.objects import User from fosspay.common import * from fosspay.network import * +import fosspay.stripe + from fosspay.blueprints.html import html app = Flask(__name__) diff --git a/fosspay/blueprints/html.py b/fosspay/blueprints/html.py index f0b4e8a..eeeb997 100644 --- a/fosspay/blueprints/html.py +++ b/fosspay/blueprints/html.py @@ -7,6 +7,7 @@ from fosspay.config import _cfg, load_config import locale import bcrypt +import hashlib encoding = locale.getdefaultlocale()[1] html = Blueprint('html', __name__, template_folder='../../templates') @@ -17,7 +18,8 @@ def index(): load_config() return render_template("setup.html") 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"]) def setup(): diff --git a/fosspay/objects.py b/fosspay/objects.py index 2f313ea..366df5e 100644 --- a/fosspay/objects.py +++ b/fosspay/objects.py @@ -58,12 +58,14 @@ class Donation(Base): type = Column(ChoiceType(DonationType, impl=String())) amount = Column(Integer, nullable=False) created = Column(DateTime, nullable=False) + emailed_about = Column(Boolean, nullable=False) def __init__(self, user, type, amount): self.user = user self.type = type self.amount = amount self.created = datetime.now() + self.emailed_about = False def __repr__(self): return "".format( diff --git a/fosspay/stripe.py b/fosspay/stripe.py new file mode 100644 index 0000000..5e5a01e --- /dev/null +++ b/fosspay/stripe.py @@ -0,0 +1,6 @@ +from fosspay.config import _cfg + +import stripe + +if _cfg("stripe-secret") != "": + stripe.api_key = _cfg("stripe-secret") diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 0000000..f1f7148 --- /dev/null +++ b/scripts/index.js @@ -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(); + }); +})(); diff --git a/templates/index.html b/templates/index.html index 45049b8..7211fd0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,9 +1,29 @@ {% extends "layout.html" %} +{% block scripts %} + + + +{% endblock %} {% block body %}

Donate to {{ _cfg("your-name") }}

-

How does this work?

+
+
+

+ 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. +

+
+
-
+ +

How much?

-
+
- +
- +
- +
- +
- + +
+
+
+
+