summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-01-17 00:06:51 -0500
committerDrew DeVault <sir@cmpwn.com>2018-01-17 00:07:54 -0500
commitd9c5b399c6ac052936f6f7b6d3002e86d0fcea32 (patch)
tree4e146643e7843ba8cbfa44a3a6bbe343c9609f7e
parentae5c35f264320d90711238b8ec0fd43056ec98ed (diff)
Add donation calculator
-rw-r--r--Dockerfile15
-rw-r--r--_config.yml2
-rw-r--r--_posts/2018-01-16-Fees-on-donation-platforms.md41
-rw-r--r--backers.html6
-rw-r--r--donation-calc/.babelrc3
-rw-r--r--donation-calc/.gitignore1
-rw-r--r--donation-calc/index.js579
-rw-r--r--donation-calc/package-lock.json3605
-rw-r--r--donation-calc/package.json29
-rw-r--r--donation-calc/webpack.config.js18
-rw-r--r--js/donation-calc.js23
-rw-r--r--js/donation-calc.js.map1
12 files changed, 4323 insertions, 0 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b640155
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM ruby:latest
+
+RUN gem install \
+ github-pages \
+ jekyll \
+ jekyll-redirect-from \
+ kramdown \
+ rdiscount \
+ rouge
+
+VOLUME /src
+EXPOSE 4000
+
+WORKDIR /src
+ENTRYPOINT ["jekyll"]
diff --git a/_config.yml b/_config.yml
index ae31b3d..1ace779 100644
--- a/_config.yml
+++ b/_config.yml
@@ -8,3 +8,5 @@ paginate_path: "/archive/page:num/"
twitter:
username: sircmpwn
logo: /avatar.png
+exclude:
+ - donation-calc
diff --git a/_posts/2018-01-16-Fees-on-donation-platforms.md b/_posts/2018-01-16-Fees-on-donation-platforms.md
new file mode 100644
index 0000000..d5a98e4
--- /dev/null
+++ b/_posts/2018-01-16-Fees-on-donation-platforms.md
@@ -0,0 +1,41 @@
+---
+layout: post
+title: Fee breakdown for various donation platforms
+---
+
+Understanding fees are a really confusing part of supporting creators of things
+you like. I provide a few ways for people to support my work, and my supporters
+can struggle to understand the differences between them. It comes down to fees,
+of which there are several kinds (note: I just made these terms up):
+
+- **Transaction fees** are charged by the payment processor (the company that
+ takes down your card number and runs the transaction with your bank). These
+ are typically in the form of a percentage of the transaction plus a few cents.
+- **Platform fees** are charged by the platform (e.g. Patreon) to run their
+ operation, typically in the form of a fixed percentage of the transaction.
+- **Withdrawl fees** are charged to move money from the platform to the
+ creator's bank account. These vary depending on the withdrawl processor.
+- **Taxes** are also implicated, depending on how much the creator makes.
+
+All of this adds up to a very confusing picture. I've made a calculator to help
+you sort it out.
+
+<noscript>Sorry, the calculator requires JavaScript.</noscript>
+<div id="react-root"></div>
+<script src="/js/donation-calc.js"></script>
+
+### Sources
+
+**fosspay**
+
+Only the typical [Stripe fee](https://stripe.com/us/pricing) is applied.
+
+**Patreon**
+
+[How do you calculate fees?](https://patreon.zendesk.com/hc/en-us/articles/204606125-How-do-you-calculate-fees-)
+
+[What are my options to receive payout?](https://patreon.zendesk.com/hc/en-us/articles/203913489-What-are-my-options-to-receive-payout-)
+
+**Liberapay**
+
+[FAQ](https://liberapay.com/about/faq)
diff --git a/backers.html b/backers.html
index 65f4094..2a1a293 100644
--- a/backers.html
+++ b/backers.html
@@ -26,6 +26,12 @@ title: Donations
and early access to some projects are available to supporters on Patreon and
fosspay!
</p>
+<p>
+ I have also written a
+ <a href="/2018/01/16/Fees-on-donation-platforms.html">
+ blog post
+ </a> comparing how fees work across each of these platforms.
+</p>
<h2>Backers</h2>
<p>
<strong>
diff --git a/donation-calc/.babelrc b/donation-calc/.babelrc
new file mode 100644
index 0000000..626ad90
--- /dev/null
+++ b/donation-calc/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["env", "stage-0", "react", "es2015"]
+}
diff --git a/donation-calc/.gitignore b/donation-calc/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/donation-calc/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/donation-calc/index.js b/donation-calc/index.js
new file mode 100644
index 0000000..fd1f053
--- /dev/null
+++ b/donation-calc/index.js
@@ -0,0 +1,579 @@
+// I sincerely apologise for the garbage you're about to see
+
+// This file is public domain, I guess. If you use this I would appreciate
+// it if you linked back to my blog, but I ain't a cop.
+
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+
+class Calculator extends Component {
+ constructor() {
+ super();
+ this.state = {
+ provider: "fosspay",
+ amount: "5.00",
+ inMethod: "card-usd",
+ inAmount: "100.00",
+ frequency: "month",
+ outMethod: "usd",
+ patreonCreators: [
+ { "id": 0, "username": "SirCmpwn", "amount": "5.00" }
+ ]
+ };
+ this.renderFosspay = this.renderFosspay.bind(this);
+ this.renderLiberapay = this.renderLiberapay.bind(this);
+ this.renderPatreon = this.renderPatreon.bind(this);
+ }
+
+ renderFosspay() {
+ const { amount } = this.state;
+ let amt = parseFloat(amount);
+ const stripe = amt * 0.029 + 0.30;
+ const creator = amt - stripe;
+ return (
+ <div>
+ <div class="form-group">
+ <label for="amount">Amount</label>
+ <div class="input-group">
+ <span class="input-group-addon">$</span>
+ <input
+ type="text"
+ class="form-control"
+ aria-label="Amount"
+ onChange={e => this.setState({ amount: e.target.value })}
+ value={amount}
+ />
+ <span class="input-group-addon">per month</span>
+ </div>
+ </div>
+ <table class="table">
+ <thead>
+ <tr>
+ <th style={{ width: "12rem" }}>You pay</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${amt.toFixed(2)}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Stripe fee</td>
+ <td>2.9% + 30c</td>
+ <td
+ style={{ textAlign: "right" }}
+ >${stripe.toFixed(2)}</td>
+ <td></td>
+ </tr>
+ <tr class="active">
+ <th>Creator earns (pre-tax)</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${creator.toFixed(2)}</th>
+ <td>per month</td>
+ </tr>
+ <tr>
+ <th></th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >{(creator / amt * 100).toFixed(0)}%</th>
+ <td>of your donation</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+
+ renderLiberapay() {
+ const { amount, inMethod, frequency, outMethod } = this.state;
+ let amt = parseFloat(this.state.amount);
+ let inAmount = parseFloat(this.state.inAmount);
+ let inFee;
+ switch (inMethod) {
+ case "card-usd":
+ inFee = inAmount * 0.02925 + 0.35;
+ break;
+ case "card-euro":
+ inFee = inAmount * 0.02106 + 0.26;
+ break;
+ case "wire":
+ inFee = inAmount * 0.00585;
+ break;
+ case "debit":
+ inFee = 0.72;
+ break;
+ }
+ let lpBalance = inAmount - inFee;
+ const creator = amt;
+ const outFee = ({
+ "sepa": 0,
+ "euro": 2.93,
+ "usd": 3.51
+ })[outMethod];
+ const creatorOut = lpBalance - outFee;
+ const duration = Math.floor(lpBalance / amt);
+ const inTypes = {
+ "card-usd": "Card",
+ "card-euro": "Card",
+ "wire": "Wire",
+ "debit": "Debit",
+ };
+ const humanInFee = {
+ "card-usd": "2.925%+35c",
+ "card-euro": "2.106%+€0.21",
+ "wire": "0.585%",
+ "debit": "€0.59",
+ };
+ return (
+ <div>
+ <p>
+ If you transfer this amount into your Liberapay wallet:
+ </p>
+ <div class="form-group">
+ <label for="amount">Amount</label>
+ <div class="input-group">
+ <span class="input-group-addon">$</span>
+ <input
+ type="text"
+ class="form-control"
+ aria-label="Amount"
+ onChange={e => this.setState({ inAmount: e.target.value })}
+ value={this.state.inAmount}
+ />
+ </div>
+ <div style={{marginTop: "1rem"}}>
+ <label>Transfer method</label>
+ </div>
+ <fieldset class="form-group">
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="inMethod"
+ checked={inMethod === "card-usd"}
+ onChange={e => this.setState({ inMethod: "card-usd" })}
+ />
+ Card - USD (2.925%+35c)
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="inMethod"
+ checked={inMethod === "card-euro"}
+ onChange={e => this.setState({ inMethod: "card-euro" })}
+ />
+ Card - Euro (2.106%+€0.21)
+ </label>
+ </fieldset>
+ <fieldset class="form-group">
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="inMethod"
+ checked={inMethod === "wire"}
+ onChange={e => this.setState({ inMethod: "wire" })}
+ />
+ Wire transfer (0.585%)
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="inMethod"
+ checked={inMethod === "debit"}
+ onChange={e => this.setState({ inMethod: "debit" })}
+ />
+ Direct debit - Euro (€0.59)
+ </label>
+ </fieldset>
+ </div>
+ <p>Your <strong>transaction fees</strong> will be:</p>
+ <table class="table">
+ <thead>
+ <tr>
+ <th style={{ width: "12rem" }}>You pay</th>
+ <th></th>
+ <th>${inAmount.toFixed(2)}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="active">
+ <td>{inTypes[inMethod]} fee</td>
+ <td>{humanInFee[inMethod]}</td>
+ <td>(${inFee.toFixed(2)})</td>
+ </tr>
+ <tr>
+ <td>Liberapay balance</td>
+ <td></td>
+ <td>${lpBalance.toFixed(2)}</td>
+ </tr>
+ </tbody>
+ </table>
+ <p>If you support your creators for this amount:</p>
+ <div class="form-group">
+ <label for="amount">Amount</label>
+ <div class="input-group">
+ <span class="input-group-addon">$</span>
+ <input
+ type="text"
+ class="form-control"
+ aria-label="Amount"
+ onChange={e => this.setState({ amount: e.target.value })}
+ value={amount}
+ />
+ </div>
+ </div>
+ <fieldset class="form-group" style={{ marginTop: "1rem" }}>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="frequency"
+ checked={frequency === "week"}
+ onChange={e => this.setState({ frequency: "week" })}
+ /> Per week
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="frequency"
+ checked={frequency === "month"}
+ onChange={e => this.setState({ frequency: "month" })}
+ /> Per month
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="frequency"
+ checked={frequency === "year"}
+ onChange={e => this.setState({ frequency: "year" })}
+ /> Per year
+ </label>
+ </fieldset>
+ <p>
+ Your creators earn <strong>${
+ creator.toFixed(2)}</strong> per {
+ frequency} for <strong>{duration} {frequency}(s)</strong>.
+ If they wait until the end of this timeframe to withdraw their funds,
+ their <strong>withdrawl fees</strong> will look like this:
+ </p>
+ <fieldset class="form-group" style={{ marginTop: "1rem" }}>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="outMethod"
+ checked={outMethod === "sepa"}
+ onChange={e => this.setState({ outMethod: "sepa" })}
+ />
+ <a href="https://en.wikipedia.org/wiki/Single_Euro_Payments_Area">
+ SEPA
+ </a> (Europe): Free
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="outMethod"
+ checked={outMethod === "euro"}
+ onChange={e => this.setState({ outMethod: "euro" })}
+ /> Other Europe: €2.93
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="outMethod"
+ checked={outMethod === "usd"}
+ onChange={e => this.setState({ outMethod: "usd" })}
+ /> USD: $3.51
+ </label>
+ </fieldset>
+ <table class="table">
+ <thead>
+ <tr>
+ <th style={{ width: "12rem" }}>You pay</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${lpBalance.toFixed(2)}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Withdrawl fee</td>
+ <td></td>
+ <td
+ style={{ textAlign: "right" }}
+ >(${outFee.toFixed(2)})</td>
+ <th></th>
+ </tr>
+ <tr class="active">
+ <th>Creator earns (pre-tax)</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${creatorOut.toFixed(2)}</th>
+ <td>
+ over {duration} {frequency}(s)
+ </td>
+ </tr>
+ <tr>
+ <th></th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >{(creatorOut / inAmount * 100).toFixed(0)}%</th>
+ <td>of your donation</td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="alert alert-info">
+ Fees in euros are for reference only. All amounts should be input in
+ USD and all computed amounts are shown in USD based on the (probably
+ dated) assumption that €1 EUR = $1.22 USD. Sorry.
+ </div>
+ </div>
+ );
+ }
+
+ renderPatreon() {
+ const creators = this.state.patreonCreators.sort(c => c.id);
+ const total = creators.map(c => parseFloat(c.amount))
+ .reduce((i, acc) => acc + i, 0);
+ const tx_fee = total * 0.029 + 0.30;
+ return (
+ <div>
+ {creators.map(creator =>
+ <div key={creator.id}>
+ <div class="form-group">
+ <label for={`user-${creator.id}`}>Creator</label>
+ <input
+ id={`user-${creator.id}`}
+ type="text"
+ class="form-control"
+ aria-label="Username"
+ onChange={e => this.setState({
+ patreonCreators: [
+ { ...creator, username: e.target.value },
+ ...creators.filter(c => c.id != creator.id)
+ ]
+ })}
+ value={creator.username}
+ />
+ </div>
+ <div class="form-group">
+ <label for="amount">Amount</label>
+ <div class="input-group">
+ <span class="input-group-addon">$</span>
+ <input
+ type="text"
+ class="form-control"
+ aria-label="Amount"
+ onChange={e => this.setState({
+ patreonCreators: [
+ { ...creator, amount: e.target.value },
+ ...creators.filter(c => c.id != creator.id)
+ ]
+ })}
+ value={creator.amount}
+ />
+ <span class="input-group-addon">per month</span>
+ </div>
+ </div>
+ </div>
+ )}
+ <div class="form-group">
+ <button
+ class="btn btn-default"
+ onClick={e => {
+ e.preventDefault();
+ this.setState({
+ patreonCreators: [
+ ...creators,
+ {
+ "id": Math.max(...creators.map(c => c.id)) + 1,
+ "username": "",
+ "amount": "5.00",
+ }
+ ]
+ });
+ }}
+ >Add another creator</button>
+ {creators.length > 1 && <button
+ class="btn btn-default"
+ style={{marginLeft: "1rem"}}
+ onClick={e => {
+ e.preventDefault();
+ this.setState({
+ patreonCreators: creators.slice(0, -1)
+ });
+ }}
+ >Remove a creator</button>}
+ </div>
+ <p>
+ You only pay one <strong>transaction fee</strong> for all of your
+ creators:
+ </p>
+ <table class="table">
+ <thead>
+ <tr>
+ <th style={{ width: "12rem" }}>You pay</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${total.toFixed(2)}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Transaction fee</td>
+ <td>2.9% + 30c</td>
+ <td
+ style={{ textAlign: "right" }}
+ >(${tx_fee.toFixed(2)})</td>
+ <td></td>
+ </tr>
+ <tr>
+ <th></th>
+ <th style={{textAlign: "right"}}>=</th>
+ <th
+ style={{ textAlign: "right" }}
+ >${(total - tx_fee).toFixed(2)}</th>
+ <th></th>
+ </tr>
+ </tbody>
+ </table>
+ <p>
+ Your total minus the transaction fee is distributed to your creators
+ like so:
+ </p>
+ <table class="table">
+ <tbody>
+ {creators.map(c => {
+ const amt = parseFloat(c.amount);
+ const share = (amt / total) * (total - tx_fee);
+ const patreon_fee = share * 0.05;
+ const after_patreon = share - patreon_fee;
+ const clamp = (value, min, max) => Math.min(Math.max(value));
+ const withdrawl_fee = clamp(after_patreon * 0.01, 0.25, 20);
+ const creator_total = after_patreon - withdrawl_fee;
+ return [
+ <tr class="active">
+ <th>{c.username}</th>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>,
+ <tr>
+ <th>You pay</th>
+ <td></td>
+ <td
+ style={{textAlign: "right"}}
+ >${amt.toFixed(2)}</td>
+ <td></td>
+ </tr>,
+ <tr>
+ <td>Transaction fee</td>
+ <td>(see above)</td>
+ <td
+ style={{textAlign: "right"}}
+ >(${(amt - share).toFixed(2)})</td>
+ <td></td>
+ </tr>,
+ <tr>
+ <td>Patreon fee</td>
+ <td>5%</td>
+ <td
+ style={{ textAlign: "right" }}
+ >(${patreon_fee.toFixed(2)})</td>
+ <td></td>
+ </tr>,
+ <tr>
+ <td>Withdrawl fee*</td>
+ <td>1% (25c min, $20 max)</td>
+ <td
+ style={{ textAlign: "right" }}
+ >(${withdrawl_fee.toFixed(2)})</td>
+ <td></td>
+ </tr>,
+ <tr>
+ <th>Creator earns (pre-tax)</th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >${creator_total.toFixed(2)}</th>
+ <td>per month</td>
+ </tr>,
+ <tr>
+ <th></th>
+ <th></th>
+ <th
+ style={{ textAlign: "right" }}
+ >{(creator_total / amt * 100).toFixed(0)}%</th>
+ <td>of your donation</td>
+ </tr>
+ ];
+ })}
+ </tbody>
+ </table>
+ <small>
+ * Withdrawl fee assumes payout via PayPal. Numbers for payout via
+ Stripe are not available, and numbers for Payoneer are different. <a
+ href="https://patreon.zendesk.com/hc/en-us/articles/203913489-What-are-my-options-to-receive-payout-">Details here</a>.
+ </small>
+ </div>
+ );
+ }
+
+ render() {
+ const { amount, provider } = this.state;
+ return (
+ <form class="calculator panel panel-default">
+ <div class="panel-heading">
+ Fee Calculator (based on 2018-01-16 fees)
+ </div>
+ <div class="panel-body">
+ <div>
+ {/* I know this is supposed to be a legend inside the fieldset */}
+ <label>Platform</label>
+ </div>
+ <fieldset class="form-group">
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="platform"
+ checked={provider === "fosspay"}
+ onChange={e => this.setState({ provider: "fosspay" })}
+ />
+ fosspay
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="platform"
+ checked={provider === "liberapay"}
+ onChange={e => this.setState({ provider: "liberapay" })}
+ />
+ Liberapay
+ </label>
+ <label class="radio-inline">
+ <input
+ type="radio"
+ name="platform"
+ checked={provider === "patreon"}
+ onChange={e => this.setState({ provider: "patreon" })}
+ />
+ Patreon
+ </label>
+ </fieldset>
+ {provider === "fosspay" && this.renderFosspay()}
+ {provider === "liberapay" && this.renderLiberapay()}
+ {provider === "patreon" && this.renderPatreon()}
+ </div>
+ </form>
+ );
+ }
+}
+
+ReactDOM.render(<Calculator />, document.getElementById("react-root"));
diff --git a/donation-calc/package-lock.json b/donation-calc/package-lock.json
new file mode 100644
index 0000000..68e9c03
--- /dev/null
+++ b/donation-calc/package-lock.json
@@ -0,0 +1,3605 @@
+{
+ "name": "donation-calculator",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "acorn": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
+ "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==",
+ "dev": true
+ },
+ "acorn-dynamic-import": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz",
+ "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
+ "dev": true,
+ "requires": {
+ "acorn": "4.0.13"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=",
+ "dev": true
+ }
+ }
+ },
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "requires": {
+ "co": "4.6.0",
+ "fast-deep-equal": "1.0.0",
+ "fast-json-stable-stringify": "2.0.0",
+ "json-schema-traverse": "0.3.1"
+ }
+ },
+ "ajv-keywords": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
+ "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
+ "dev": true
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2",
+ "longest": "1.0.1",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "dev": true,
+ "requires": {
+ "micromatch": "2.3.11",
+ "normalize-path": "2.1.1"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "1.1.0"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz",
+ "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.0"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+ "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+ "dev": true,
+ "requires": {
+ "util": "0.10.3"
+ }
+ },
+ "async": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
+ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
+ "dev": true,
+ "requires": {
+ "lodash": "4.17.4"
+ }
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+ "dev": true
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "esutils": "2.0.2",
+ "js-tokens": "3.0.2"
+ }
+ },
+ "babel-core": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz",
+ "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-generator": "6.26.0",
+ "babel-helpers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "convert-source-map": "1.5.1",
+ "debug": "2.6.9",
+ "json5": "0.5.1",
+ "lodash": "4.17.4",
+ "minimatch": "3.0.4",
+ "path-is-absolute": "1.0.1",
+ "private": "0.1.8",
+ "slash": "1.0.0",
+ "source-map": "0.5.7"
+ }
+ },
+ "babel-generator": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz",
+ "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=",
+ "dev": true,
+ "requires": {
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "detect-indent": "4.0.0",
+ "jsesc": "1.3.0",
+ "lodash": "4.17.4",
+ "source-map": "0.5.7",
+ "trim-right": "1.0.1"
+ }
+ },
+ "babel-helper-bindify-decorators": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
+ "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-builder-binary-assignment-operator-visitor": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
+ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
+ "dev": true,
+ "requires": {
+ "babel-helper-explode-assignable-expression": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-builder-react-jsx": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
+ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "esutils": "2.0.2"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+ "dev": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "